From 8ac442500adc9db09e3c6844c36f78382ce255bc Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 25 Mar 2015 14:49:04 +0000 Subject: [PATCH] Import openni2_2.2.0.33+dfsg.orig.tar.gz [dgit import orig openni2_2.2.0.33+dfsg.orig.tar.gz] --- .gitignore | 22 + Android.mk | 39 + Application.mk | 25 + CHANGES.txt | 89 + Config/OpenNI.ini | 14 + Config/OpenNI2/Drivers/PS1080.ini | 158 + Config/OpenNI2/Drivers/PSLink.ini | 45 + Include/Android-Arm/OniPlatformAndroid-Arm.h | 43 + Include/Driver/OniDriverAPI.h | 378 + Include/Driver/OniDriverTypes.h | 54 + Include/Linux-Arm/OniPlatformLinux-Arm.h | 36 + Include/Linux-x86/OniPlatformLinux-x86.h | 102 + Include/MacOSX/OniPlatformMacOSX.h | 42 + Include/OniCAPI.h | 259 + Include/OniCEnums.h | 84 + Include/OniCProperties.h | 68 + Include/OniCTypes.h | 193 + Include/OniEnums.h | 86 + Include/OniPlatform.h | 72 + Include/OniProperties.h | 73 + Include/OniVersion.h | 43 + Include/OpenNI.h | 2750 +++ Include/PS1080.h | 632 + Include/PSLink.h | 199 + Include/PrimeSense.h | 229 + Include/Win32/OniPlatformWin32.h | 139 + LICENSE | 202 + Makefile | 160 + NOTICE | 15 + OpenNI.sln | 327 + Packaging/Harvest.py | 343 + Packaging/Install/Fragments/.gitignore | 1 + Packaging/Install/Includes/Variables.wxi | 44 + Packaging/Install/Install.wixproj | 226 + Packaging/Install/Install.wxs | 247 + Packaging/Install/Lang/en-us/Loc_en-us.wxl | 13 + .../Resources/Microsoft_VC100_CRT_x64.msm | Bin 0 -> 585728 bytes .../Resources/Microsoft_VC100_CRT_x86.msm | Bin 0 -> 573440 bytes .../Resources/Microsoft_VC90_CRT_x86.msm | Bin 0 -> 607744 bytes .../Resources/Microsoft_VC90_CRT_x86_x64.msm | Bin 0 -> 637440 bytes .../Install/Resources/OpenNIBackground.bmp | Bin 0 -> 155830 bytes Packaging/Install/Resources/OpenNIHeader.bmp | Bin 0 -> 29846 bytes Packaging/Install/Resources/mainicon.ico | Bin 0 -> 10534 bytes Packaging/Install/UI/CustomeInstallUI.wxs | 82 + Packaging/Install/setup.exe | Bin 0 -> 126464 bytes Packaging/Linux/install.sh | 46 + Packaging/Linux/primesense-usb.rules | 14 + Packaging/ReleaseVersion.py | 185 + Packaging/UpdateVersion.py | 144 + README | 94 + ReleaseNotes.txt | 28 + Samples/Android.mk | 20 + .../ClosestPointViewer.vcxproj | 188 + Samples/ClosestPointViewer/Makefile | 27 + Samples/ClosestPointViewer/Viewer.cpp | 266 + Samples/ClosestPointViewer/Viewer.h | 101 + Samples/ClosestPointViewer/main.cpp | 41 + Samples/Common/OniSampleUtilities.h | 119 + Samples/EventBasedRead/Android.mk | 41 + Samples/EventBasedRead/EventBasedRead.vcxproj | 172 + Samples/EventBasedRead/Makefile | 17 + Samples/EventBasedRead/main.cpp | 156 + Samples/MWClosestPoint/MWClosestPoint.cpp | 215 + Samples/MWClosestPoint/MWClosestPoint.h | 82 + Samples/MWClosestPoint/MWClosestPoint.vcxproj | 189 + .../MWClosestPoint.vcxproj.filters | 27 + Samples/MWClosestPoint/Makefile | 16 + .../MWClosestPointApp.vcxproj | 182 + .../MWClosestPointApp.vcxproj.filters | 22 + Samples/MWClosestPointApp/Makefile | 18 + Samples/MWClosestPointApp/main.cpp | 126 + Samples/MultiDepthViewer/Makefile | 26 + .../MultiDepthViewer/MultiDepthViewer.vcxproj | 176 + Samples/MultiDepthViewer/Viewer.cpp | 332 + Samples/MultiDepthViewer/Viewer.h | 82 + Samples/MultiDepthViewer/main.cpp | 139 + Samples/MultipleStreamRead/Android.mk | 41 + Samples/MultipleStreamRead/Makefile | 17 + .../MultipleStreamRead.vcxproj | 172 + Samples/MultipleStreamRead/main.cpp | 150 + Samples/SimpleRead/Android.mk | 41 + Samples/SimpleRead/Makefile | 17 + Samples/SimpleRead/SimpleRead.vcxproj | 172 + Samples/SimpleRead/main.cpp | 105 + Samples/SimpleViewer.java/Build.bat | 1 + Samples/SimpleViewer.java/Makefile | 12 + .../Samples/SimpleViewer/SimpleViewer.java | 140 + .../SimpleViewer/SimpleViewerApplication.java | 203 + Samples/SimpleViewer/Makefile | 26 + Samples/SimpleViewer/SimpleViewer.vcxproj | 176 + Samples/SimpleViewer/Viewer.cpp | 323 + Samples/SimpleViewer/Viewer.h | 81 + Samples/SimpleViewer/main.cpp | 94 + Source/Android.mk | 17 + Source/Core/Android.mk | 67 + Source/Core/Makefile | 35 + Source/Core/OniCommon.h | 32 + Source/Core/OniContext.cpp | 1063 + Source/Core/OniContext.h | 142 + Source/Core/OniDataRecords.cpp | 454 + Source/Core/OniDataRecords.h | 247 + Source/Core/OniDebug.h | 170 + Source/Core/OniDevice.cpp | 305 + Source/Core/OniDevice.h | 107 + Source/Core/OniDeviceDriver.cpp | 178 + Source/Core/OniDeviceDriver.h | 86 + Source/Core/OniDriverHandler.cpp | 102 + Source/Core/OniDriverHandler.h | 261 + Source/Core/OniDriverServices.h | 43 + Source/Core/OniFrameHolder.h | 86 + Source/Core/OniFrameManager.cpp | 69 + Source/Core/OniFrameManager.h | 40 + Source/Core/OniInternal.h | 40 + Source/Core/OniRecorder.cpp | 1112 + Source/Core/OniRecorder.h | 251 + Source/Core/OniSensor.cpp | 246 + Source/Core/OniSensor.h | 82 + Source/Core/OniStream.cpp | 470 + Source/Core/OniStream.h | 150 + Source/Core/OniStreamFrameHolder.cpp | 147 + Source/Core/OniStreamFrameHolder.h | 71 + Source/Core/OniSyncedStreamsFrameHolder.cpp | 366 + Source/Core/OniSyncedStreamsFrameHolder.h | 84 + Source/Core/OpenNI.cpp | 517 + Source/Core/OpenNI.vcxproj | 711 + Source/Core/OpenNI.vcxproj.filters | 316 + Source/Core/README.txt | 40 + Source/DepthUtils/Android.mk | 38 + Source/DepthUtils/DepthUtils.cpp | 65 + Source/DepthUtils/DepthUtils.h | 97 + Source/DepthUtils/DepthUtils.vcxproj | 186 + Source/DepthUtils/DepthUtilsImpl.cpp | 484 + Source/DepthUtils/DepthUtilsImpl.h | 77 + Source/DepthUtils/Makefile | 26 + Source/Documentation/Doxyfile | 1817 ++ Source/Documentation/OpenNILogo.bmp | Bin 0 -> 21030 bytes Source/Documentation/Runme.py | 52 + Source/Documentation/Text/Conventions.txt | 58 + Source/Documentation/Text/GettingStarted.txt | 89 + Source/Documentation/Text/MainPage.txt | 11 + Source/Drivers/Android.mk | 17 + Source/Drivers/DummyDevice/DummyDevice.cpp | 478 + .../Drivers/DummyDevice/DummyDevice.vcxproj | 194 + Source/Drivers/DummyDevice/Makefile | 30 + Source/Drivers/Kinect/BaseKinectStream.cpp | 179 + Source/Drivers/Kinect/BaseKinectStream.h | 63 + Source/Drivers/Kinect/ColorKinectStream.cpp | 192 + Source/Drivers/Kinect/ColorKinectStream.h | 27 + Source/Drivers/Kinect/D2S.h.h | 1003 + Source/Drivers/Kinect/DepthKinectStream.cpp | 386 + Source/Drivers/Kinect/DepthKinectStream.h | 34 + Source/Drivers/Kinect/IRKinectStream.cpp | 172 + Source/Drivers/Kinect/IRKinectStream.h | 21 + Source/Drivers/Kinect/Kinect.vcxproj | 198 + Source/Drivers/Kinect/Kinect.vcxproj.filters | 33 + Source/Drivers/Kinect/KinectDevice.cpp | 187 + Source/Drivers/Kinect/KinectDevice.h | 41 + Source/Drivers/Kinect/KinectDriver.cpp | 219 + Source/Drivers/Kinect/KinectDriver.h | 36 + Source/Drivers/Kinect/KinectStreamImpl.cpp | 319 + Source/Drivers/Kinect/KinectStreamImpl.h | 82 + Source/Drivers/Kinect/S2D.h.h | 207 + Source/Drivers/OniFile/Android.mk | 63 + Source/Drivers/OniFile/DataRecords.cpp | 1070 + Source/Drivers/OniFile/DataRecords.h | 389 + Source/Drivers/OniFile/Formats/Xn16zCodec.h | 55 + .../OniFile/Formats/Xn16zEmbTablesCodec.h | 60 + Source/Drivers/OniFile/Formats/Xn8zCodec.h | 54 + Source/Drivers/OniFile/Formats/XnCodec.cpp | 63 + Source/Drivers/OniFile/Formats/XnCodec.h | 55 + Source/Drivers/OniFile/Formats/XnCodecBase.h | 86 + Source/Drivers/OniFile/Formats/XnCodecIDs.h | 34 + Source/Drivers/OniFile/Formats/XnJpegCodec.h | 99 + .../OniFile/Formats/XnStreamCompression.cpp | 1314 ++ .../OniFile/Formats/XnStreamCompression.h | 103 + .../Drivers/OniFile/Formats/XnStreamFormats.h | 49 + .../OniFile/Formats/XnUncompressedCodec.h | 71 + Source/Drivers/OniFile/Makefile | 37 + Source/Drivers/OniFile/OniFile.vcxproj | 752 + .../Drivers/OniFile/OniFile.vcxproj.filters | 257 + Source/Drivers/OniFile/PlayerCodecFactory.cpp | 128 + Source/Drivers/OniFile/PlayerCodecFactory.h | 44 + Source/Drivers/OniFile/PlayerDevice.cpp | 1147 + Source/Drivers/OniFile/PlayerDevice.h | 195 + Source/Drivers/OniFile/PlayerDriver.cpp | 145 + Source/Drivers/OniFile/PlayerDriver.h | 76 + Source/Drivers/OniFile/PlayerNode.cpp | 1806 ++ Source/Drivers/OniFile/PlayerNode.h | 195 + Source/Drivers/OniFile/PlayerProperties.h | 122 + Source/Drivers/OniFile/PlayerSource.cpp | 159 + Source/Drivers/OniFile/PlayerSource.h | 109 + Source/Drivers/OniFile/PlayerStream.cpp | 249 + Source/Drivers/OniFile/PlayerStream.h | 140 + Source/Drivers/OniFile/XnPlayerTypes.h | 365 + Source/Drivers/OniFile/XnPropNames.h | 65 + Source/Drivers/PS1080/Android.mk | 67 + Source/Drivers/PS1080/Core/XnBuffer.cpp | 56 + Source/Drivers/PS1080/Core/XnBuffer.h | 189 + Source/Drivers/PS1080/Core/XnCore.cpp | 83 + Source/Drivers/PS1080/Core/XnCoreGlobals.h | 58 + Source/Drivers/PS1080/Core/XnCoreStatus.cpp | 27 + Source/Drivers/PS1080/Core/XnIOFileStream.cpp | 78 + .../Drivers/PS1080/Core/XnIONetworkStream.cpp | 101 + .../PS1080/DDK/XnActualGeneralProperty.cpp | 86 + .../PS1080/DDK/XnActualGeneralProperty.h | 81 + .../PS1080/DDK/XnActualIntProperty.cpp | 46 + .../Drivers/PS1080/DDK/XnActualIntProperty.h | 68 + .../PS1080/DDK/XnActualPropertiesHash.cpp | 233 + .../PS1080/DDK/XnActualPropertiesHash.h | 69 + .../PS1080/DDK/XnActualRealProperty.cpp | 46 + .../Drivers/PS1080/DDK/XnActualRealProperty.h | 68 + .../PS1080/DDK/XnActualStringProperty.cpp | 46 + .../PS1080/DDK/XnActualStringProperty.h | 68 + Source/Drivers/PS1080/DDK/XnAudioStream.cpp | 103 + Source/Drivers/PS1080/DDK/XnAudioStream.h | 83 + Source/Drivers/PS1080/DDK/XnCodecFactory.cpp | 113 + Source/Drivers/PS1080/DDK/XnCodecFactory.h | 42 + Source/Drivers/PS1080/DDK/XnDDK.cpp | 106 + Source/Drivers/PS1080/DDK/XnDDKStatus.cpp | 26 + Source/Drivers/PS1080/DDK/XnDepthStream.cpp | 133 + Source/Drivers/PS1080/DDK/XnDepthStream.h | 119 + Source/Drivers/PS1080/DDK/XnDeviceBase.cpp | 1152 + Source/Drivers/PS1080/DDK/XnDeviceBase.h | 232 + Source/Drivers/PS1080/DDK/XnDeviceModule.cpp | 737 + Source/Drivers/PS1080/DDK/XnDeviceModule.h | 117 + .../PS1080/DDK/XnDeviceModuleHolder.cpp | 56 + .../Drivers/PS1080/DDK/XnDeviceModuleHolder.h | 54 + Source/Drivers/PS1080/DDK/XnDeviceStream.cpp | 230 + Source/Drivers/PS1080/DDK/XnDeviceStream.h | 142 + .../PS1080/DDK/XnFrameBufferManager.cpp | 143 + .../Drivers/PS1080/DDK/XnFrameBufferManager.h | 80 + Source/Drivers/PS1080/DDK/XnFrameStream.cpp | 104 + Source/Drivers/PS1080/DDK/XnFrameStream.h | 93 + .../Drivers/PS1080/DDK/XnGeneralProperty.cpp | 82 + Source/Drivers/PS1080/DDK/XnGeneralProperty.h | 88 + Source/Drivers/PS1080/DDK/XnIRStream.cpp | 32 + Source/Drivers/PS1080/DDK/XnIRStream.h | 40 + Source/Drivers/PS1080/DDK/XnImageStream.cpp | 32 + Source/Drivers/PS1080/DDK/XnImageStream.h | 40 + Source/Drivers/PS1080/DDK/XnIntProperty.cpp | 95 + Source/Drivers/PS1080/DDK/XnIntProperty.h | 85 + .../PS1080/DDK/XnIntPropertySynchronizer.cpp | 116 + .../PS1080/DDK/XnIntPropertySynchronizer.h | 50 + Source/Drivers/PS1080/DDK/XnPixelStream.cpp | 475 + Source/Drivers/PS1080/DDK/XnPixelStream.h | 138 + Source/Drivers/PS1080/DDK/XnProperty.cpp | 193 + Source/Drivers/PS1080/DDK/XnProperty.h | 144 + Source/Drivers/PS1080/DDK/XnPropertySet.cpp | 668 + .../PS1080/DDK/XnPropertySetInternal.h | 58 + Source/Drivers/PS1080/DDK/XnRealProperty.cpp | 93 + Source/Drivers/PS1080/DDK/XnRealProperty.h | 85 + Source/Drivers/PS1080/DDK/XnShiftToDepth.cpp | 145 + Source/Drivers/PS1080/DDK/XnShiftToDepth.h | 78 + .../PS1080/DDK/XnShiftToDepthStreamHelper.cpp | 327 + .../PS1080/DDK/XnShiftToDepthStreamHelper.h | 65 + .../Drivers/PS1080/DDK/XnStreamingStream.cpp | 66 + Source/Drivers/PS1080/DDK/XnStreamingStream.h | 71 + .../Drivers/PS1080/DDK/XnStringProperty.cpp | 92 + Source/Drivers/PS1080/DDK/XnStringProperty.h | 87 + .../PS1080/DriverImpl/XnExportedOniDriver.cpp | 32 + .../PS1080/DriverImpl/XnOniColorStream.cpp | 147 + .../PS1080/DriverImpl/XnOniColorStream.h | 45 + .../PS1080/DriverImpl/XnOniDepthStream.cpp | 184 + .../PS1080/DriverImpl/XnOniDepthStream.h | 44 + .../Drivers/PS1080/DriverImpl/XnOniDevice.cpp | 532 + .../Drivers/PS1080/DriverImpl/XnOniDevice.h | 88 + .../Drivers/PS1080/DriverImpl/XnOniDriver.cpp | 282 + .../Drivers/PS1080/DriverImpl/XnOniDriver.h | 84 + .../PS1080/DriverImpl/XnOniIRStream.cpp | 35 + .../Drivers/PS1080/DriverImpl/XnOniIRStream.h | 40 + .../PS1080/DriverImpl/XnOniMapStream.cpp | 365 + .../PS1080/DriverImpl/XnOniMapStream.h | 69 + .../Drivers/PS1080/DriverImpl/XnOniStream.cpp | 178 + .../Drivers/PS1080/DriverImpl/XnOniStream.h | 77 + Source/Drivers/PS1080/Formats/Xn16zCodec.h | 53 + .../PS1080/Formats/Xn16zEmbTablesCodec.h | 58 + Source/Drivers/PS1080/Formats/Xn8zCodec.h | 52 + Source/Drivers/PS1080/Formats/XnCodec.h | 55 + Source/Drivers/PS1080/Formats/XnCodecBase.h | 86 + Source/Drivers/PS1080/Formats/XnCodecs.cpp | 68 + Source/Drivers/PS1080/Formats/XnFormats.cpp | 34 + Source/Drivers/PS1080/Formats/XnFormats.h | 51 + .../PS1080/Formats/XnFormatsMirror.cpp | 242 + .../PS1080/Formats/XnFormatsStatus.cpp | 27 + Source/Drivers/PS1080/Formats/XnJpegCodec.h | 96 + Source/Drivers/PS1080/Formats/XnNiCodec.h | 55 + .../PS1080/Formats/XnStreamCompression.cpp | 1280 + .../PS1080/Formats/XnStreamCompression.h | 104 + .../PS1080/Formats/XnUncompressedCodec.h | 68 + Source/Drivers/PS1080/Include/XnCommon.h | 55 + Source/Drivers/PS1080/Include/XnCore.h | 107 + Source/Drivers/PS1080/Include/XnDDK.h | 53 + Source/Drivers/PS1080/Include/XnDDKStatus.h | 162 + Source/Drivers/PS1080/Include/XnDevice.h | 55 + .../Drivers/PS1080/Include/XnDeviceProto.inl | 400 + Source/Drivers/PS1080/Include/XnDeviceProxy.h | 98 + .../Drivers/PS1080/Include/XnFormatsStatus.h | 63 + .../Drivers/PS1080/Include/XnIOFileStream.h | 53 + .../PS1080/Include/XnIONetworkStream.h | 53 + Source/Drivers/PS1080/Include/XnIOStream.h | 46 + Source/Drivers/PS1080/Include/XnPlatformBC.h | 55 + Source/Drivers/PS1080/Include/XnPropertySet.h | 250 + Source/Drivers/PS1080/Include/XnPsVersion.h | 57 + .../Drivers/PS1080/Include/XnStreamFormats.h | 50 + .../Drivers/PS1080/Include/XnStreamParams.h | 260 + Source/Drivers/PS1080/Makefile | 44 + Source/Drivers/PS1080/PS1080.vcxproj | 699 + Source/Drivers/PS1080/PS1080.vcxproj.filters | 823 + Source/Drivers/PS1080/PS1080Console/Makefile | 32 + .../PS1080/PS1080Console/PS1080Console.cpp | 1772 ++ .../PS1080Console/PS1080Console.vcxproj | 191 + .../PS1080Console.vcxproj.filters | 18 + Source/Drivers/PS1080/Sensor/Bayer.cpp | 1158 + Source/Drivers/PS1080/Sensor/Bayer.h | 42 + .../Drivers/PS1080/Sensor/IXnSensorStream.h | 51 + Source/Drivers/PS1080/Sensor/Uncomp.cpp | 313 + Source/Drivers/PS1080/Sensor/Uncomp.h | 43 + .../PS1080/Sensor/XnAudioProcessor.cpp | 143 + .../Drivers/PS1080/Sensor/XnAudioProcessor.h | 73 + .../PS1080/Sensor/XnBayerImageProcessor.cpp | 161 + .../PS1080/Sensor/XnBayerImageProcessor.h | 57 + Source/Drivers/PS1080/Sensor/XnCmosInfo.cpp | 82 + Source/Drivers/PS1080/Sensor/XnCmosInfo.h | 60 + .../Drivers/PS1080/Sensor/XnDataProcessor.cpp | 237 + .../Drivers/PS1080/Sensor/XnDataProcessor.h | 102 + .../PS1080/Sensor/XnDataProcessorHolder.cpp | 93 + .../PS1080/Sensor/XnDataProcessorHolder.h | 53 + .../PS1080/Sensor/XnDepthProcessor.cpp | 205 + .../Drivers/PS1080/Sensor/XnDepthProcessor.h | 92 + .../PS1080/Sensor/XnDeviceEnumeration.cpp | 199 + .../PS1080/Sensor/XnDeviceEnumeration.h | 69 + Source/Drivers/PS1080/Sensor/XnDeviceSensor.h | 502 + .../PS1080/Sensor/XnDeviceSensorIO.cpp | 347 + .../Drivers/PS1080/Sensor/XnDeviceSensorIO.h | 100 + .../PS1080/Sensor/XnDeviceSensorInit.cpp | 247 + .../PS1080/Sensor/XnDeviceSensorInit.h | 97 + .../PS1080/Sensor/XnDeviceSensorProtocol.cpp | 573 + .../PS1080/Sensor/XnDeviceSensorProtocol.h | 134 + .../PS1080/Sensor/XnFirmwareCommands.cpp | 48 + .../PS1080/Sensor/XnFirmwareCommands.h | 50 + .../Drivers/PS1080/Sensor/XnFirmwareInfo.cpp | 24 + Source/Drivers/PS1080/Sensor/XnFirmwareInfo.h | 125 + .../PS1080/Sensor/XnFirmwareStreams.cpp | 396 + .../Drivers/PS1080/Sensor/XnFirmwareStreams.h | 86 + .../Drivers/PS1080/Sensor/XnFirmwareTypes.h | 38 + .../PS1080/Sensor/XnFrameStreamProcessor.cpp | 163 + .../PS1080/Sensor/XnFrameStreamProcessor.h | 183 + .../PS1080/Sensor/XnGMCDebugProcessor.cpp | 110 + .../PS1080/Sensor/XnGMCDebugProcessor.h | 54 + .../PS1080/Sensor/XnGeneralDebugProcessor.cpp | 55 + .../PS1080/Sensor/XnGeneralDebugProcessor.h | 39 + .../Drivers/PS1080/Sensor/XnHostProtocol.cpp | 3448 +++ Source/Drivers/PS1080/Sensor/XnHostProtocol.h | 376 + .../Drivers/PS1080/Sensor/XnIRProcessor.cpp | 347 + Source/Drivers/PS1080/Sensor/XnIRProcessor.h | 72 + .../PS1080/Sensor/XnImageProcessor.cpp | 161 + .../Drivers/PS1080/Sensor/XnImageProcessor.h | 76 + .../PS1080/Sensor/XnJpegImageProcessor.cpp | 56 + .../PS1080/Sensor/XnJpegImageProcessor.h | 44 + .../Sensor/XnJpegToRGBImageProcessor.cpp | 109 + .../PS1080/Sensor/XnJpegToRGBImageProcessor.h | 58 + .../PS1080/Sensor/XnNesaDebugProcessor.cpp | 55 + .../PS1080/Sensor/XnNesaDebugProcessor.h | 52 + .../Sensor/XnPSCompressedDepthProcessor.cpp | 295 + .../Sensor/XnPSCompressedDepthProcessor.h | 66 + .../Sensor/XnPSCompressedImageProcessor.cpp | 162 + .../Sensor/XnPSCompressedImageProcessor.h | 57 + .../Sensor/XnPacked11DepthProcessor.cpp | 213 + .../PS1080/Sensor/XnPacked11DepthProcessor.h | 62 + .../Sensor/XnPacked12DepthProcessor.cpp | 316 + .../PS1080/Sensor/XnPacked12DepthProcessor.h | 62 + Source/Drivers/PS1080/Sensor/XnParams.h | 134 + .../Sensor/XnPassThroughImageProcessor.cpp | 54 + .../Sensor/XnPassThroughImageProcessor.h | 46 + Source/Drivers/PS1080/Sensor/XnSensor.cpp | 1959 ++ Source/Drivers/PS1080/Sensor/XnSensor.h | 307 + .../PS1080/Sensor/XnSensorAudioStream.cpp | 478 + .../PS1080/Sensor/XnSensorAudioStream.h | 122 + .../PS1080/Sensor/XnSensorDepthStream.cpp | 1291 + .../PS1080/Sensor/XnSensorDepthStream.h | 192 + Source/Drivers/PS1080/Sensor/XnSensorFPS.cpp | 79 + Source/Drivers/PS1080/Sensor/XnSensorFPS.h | 58 + .../PS1080/Sensor/XnSensorFirmware.cpp | 152 + .../Drivers/PS1080/Sensor/XnSensorFirmware.h | 59 + .../PS1080/Sensor/XnSensorFirmwareParams.cpp | 655 + .../PS1080/Sensor/XnSensorFirmwareParams.h | 161 + .../PS1080/Sensor/XnSensorFixedParams.cpp | 104 + .../PS1080/Sensor/XnSensorFixedParams.h | 87 + .../PS1080/Sensor/XnSensorIRStream.cpp | 491 + .../Drivers/PS1080/Sensor/XnSensorIRStream.h | 109 + .../PS1080/Sensor/XnSensorImageStream.cpp | 943 + .../PS1080/Sensor/XnSensorImageStream.h | 159 + .../PS1080/Sensor/XnSensorStreamHelper.cpp | 419 + .../PS1080/Sensor/XnSensorStreamHelper.h | 143 + .../PS1080/Sensor/XnStreamProcessor.cpp | 38 + .../Drivers/PS1080/Sensor/XnStreamProcessor.h | 57 + .../PS1080/Sensor/XnTecDebugProcessor.cpp | 60 + .../PS1080/Sensor/XnTecDebugProcessor.h | 52 + .../Sensor/XnUncompressedBayerProcessor.cpp | 108 + .../Sensor/XnUncompressedBayerProcessor.h | 55 + .../Sensor/XnUncompressedDepthProcessor.cpp | 78 + .../Sensor/XnUncompressedDepthProcessor.h | 46 + ...nUncompressedYUV422toRGBImageProcessor.cpp | 113 + .../XnUncompressedYUV422toRGBImageProcessor.h | 53 + .../XnUncompressedYUYVtoRGBImageProcessor.cpp | 113 + .../XnUncompressedYUYVtoRGBImageProcessor.h | 53 + .../XnWavelengthCorrectionDebugProcessor.cpp | 60 + .../XnWavelengthCorrectionDebugProcessor.h | 51 + .../PS1080/Sensor/XnWholePacketProcessor.cpp | 77 + .../PS1080/Sensor/XnWholePacketProcessor.h | 59 + Source/Drivers/PS1080/Sensor/YUV.cpp | 362 + Source/Drivers/PS1080/Sensor/YUV.h | 69 + Source/Drivers/PS1080/XnPsVersion.h | 57 + Source/Drivers/PSLink/Android.mk | 41 + .../DriverImpl/LinkExportedOniDriver.cpp | 32 + .../PSLink/DriverImpl/LinkOniDepthStream.cpp | 229 + .../PSLink/DriverImpl/LinkOniDepthStream.h | 42 + .../PSLink/DriverImpl/LinkOniDevice.cpp | 1323 ++ .../Drivers/PSLink/DriverImpl/LinkOniDevice.h | 102 + .../PSLink/DriverImpl/LinkOniDriver.cpp | 304 + .../Drivers/PSLink/DriverImpl/LinkOniDriver.h | 91 + .../PSLink/DriverImpl/LinkOniIRStream.cpp | 35 + .../PSLink/DriverImpl/LinkOniIRStream.h | 39 + .../PSLink/DriverImpl/LinkOniMapStream.cpp | 331 + .../PSLink/DriverImpl/LinkOniMapStream.h | 69 + .../PSLink/DriverImpl/LinkOniStream.cpp | 186 + .../Drivers/PSLink/DriverImpl/LinkOniStream.h | 84 + .../Drivers/PSLink/LinkDeviceEnumeration.cpp | 174 + Source/Drivers/PSLink/LinkDeviceEnumeration.h | 68 + .../LinkProtoLib/IAsyncInputConnection.h | 27 + .../Drivers/PSLink/LinkProtoLib/IConnection.h | 20 + .../PSLink/LinkProtoLib/IConnectionFactory.h | 34 + .../PSLink/LinkProtoLib/ILinkOutputStream.h | 40 + .../PSLink/LinkProtoLib/IOutputConnection.h | 25 + .../PSLink/LinkProtoLib/ISyncIOConnection.h | 38 + .../LinkProtoLib/ISyncInputConnection.h | 19 + .../PSLink/LinkProtoLib/LinkProtoLibVersion.h | 25 + .../XnClientSocketInConnection.cpp | 34 + .../LinkProtoLib/XnClientSocketInConnection.h | 20 + .../XnClientSyncSocketConnection.cpp | 0 .../XnClientSyncSocketConnection.h | 11 + .../XnClientUSBConnectionFactory.cpp | 195 + .../XnClientUSBConnectionFactory.h | 57 + .../XnClientUSBControlEndpoint.cpp | 90 + .../LinkProtoLib/XnClientUSBControlEndpoint.h | 45 + .../XnClientUSBInDataEndpoint.cpp | 152 + .../LinkProtoLib/XnClientUSBInDataEndpoint.h | 57 + .../XnClientUSBOutDataEndpoint.cpp | 110 + .../LinkProtoLib/XnClientUSBOutDataEndpoint.h | 43 + .../PSLink/LinkProtoLib/XnCyclicBuffer.h | 123 + .../LinkProtoLib/XnLink11BitS2DParser.cpp | 126 + .../LinkProtoLib/XnLink11BitS2DParser.h | 31 + .../LinkProtoLib/XnLink12BitS2DParser.cpp | 264 + .../LinkProtoLib/XnLink12BitS2DParser.h | 39 + .../PSLink/LinkProtoLib/XnLink16zParser.cpp | 196 + .../PSLink/LinkProtoLib/XnLink16zParser.h | 44 + .../LinkProtoLib/XnLink24zYuv422Parser.cpp | 232 + .../LinkProtoLib/XnLink24zYuv422Parser.h | 44 + .../PSLink/LinkProtoLib/XnLink6BitParser.cpp | 88 + .../PSLink/LinkProtoLib/XnLink6BitParser.h | 29 + .../LinkProtoLib/XnLinkContInputStream.cpp | 290 + .../LinkProtoLib/XnLinkContInputStream.h | 79 + .../LinkProtoLib/XnLinkControlEndpoint.cpp | 1742 ++ .../LinkProtoLib/XnLinkControlEndpoint.h | 166 + .../LinkProtoLib/XnLinkFrameInputStream.cpp | 829 + .../LinkProtoLib/XnLinkFrameInputStream.h | 137 + .../LinkProtoLib/XnLinkInputDataEndpoint.cpp | 161 + .../LinkProtoLib/XnLinkInputDataEndpoint.h | 58 + .../PSLink/LinkProtoLib/XnLinkInputStream.cpp | 173 + .../PSLink/LinkProtoLib/XnLinkInputStream.h | 73 + .../LinkProtoLib/XnLinkInputStreamsMgr.cpp | 338 + .../LinkProtoLib/XnLinkInputStreamsMgr.h | 69 + .../PSLink/LinkProtoLib/XnLinkLogParser.cpp | 201 + .../PSLink/LinkProtoLib/XnLinkLogParser.h | 35 + .../PSLink/LinkProtoLib/XnLinkMsgEncoder.cpp | 175 + .../PSLink/LinkProtoLib/XnLinkMsgEncoder.h | 63 + .../PSLink/LinkProtoLib/XnLinkMsgParser.cpp | 87 + .../PSLink/LinkProtoLib/XnLinkMsgParser.h | 40 + .../LinkProtoLib/XnLinkOutputDataEndpoint.cpp | 108 + .../LinkProtoLib/XnLinkOutputDataEndpoint.h | 40 + .../LinkProtoLib/XnLinkOutputStream.cpp | 114 + .../PSLink/LinkProtoLib/XnLinkOutputStream.h | 48 + .../LinkProtoLib/XnLinkOutputStreamsMgr.cpp | 134 + .../LinkProtoLib/XnLinkOutputStreamsMgr.h | 49 + .../LinkProtoLib/XnLinkPacked10BitParser.cpp | 88 + .../LinkProtoLib/XnLinkPacked10BitParser.h | 27 + .../PSLink/LinkProtoLib/XnLinkProtoLibDefs.h | 69 + .../PSLink/LinkProtoLib/XnLinkProtoUtils.cpp | 1160 + .../PSLink/LinkProtoLib/XnLinkProtoUtils.h | 163 + .../LinkProtoLib/XnLinkResponseMsgParser.cpp | 59 + .../LinkProtoLib/XnLinkResponseMsgParser.h | 22 + .../PSLink/LinkProtoLib/XnLinkStatusCodes.cpp | 4 + .../PSLink/LinkProtoLib/XnLinkStatusCodes.h | 118 + .../XnLinkUnpackedDataReductionParser.cpp | 50 + .../XnLinkUnpackedDataReductionParser.h | 32 + .../LinkProtoLib/XnLinkUnpackedS2DParser.cpp | 47 + .../LinkProtoLib/XnLinkUnpackedS2DParser.h | 28 + .../XnLinkYuv422ToRgb888Parser.cpp | 32 + .../LinkProtoLib/XnLinkYuv422ToRgb888Parser.h | 26 + .../PSLink/LinkProtoLib/XnLinkYuvToRgb.cpp | 154 + .../PSLink/LinkProtoLib/XnLinkYuvToRgb.h | 20 + .../LinkProtoLib/XnMapSequenceListConverter.h | 206 + .../XnServerSocketInConnection.cpp | 49 + .../LinkProtoLib/XnServerSocketInConnection.h | 24 + .../XnServerUSBLinuxConnectionFactory.h | 40 + .../XnServerUSBLinuxControlEndpoint.h | 37 + .../XnServerUSBLinuxOutDataEndpoint.h | 33 + .../PSLink/LinkProtoLib/XnShiftToDepth.cpp | 136 + .../PSLink/LinkProtoLib/XnShiftToDepth.h | 67 + .../XnSocketConnectionFactory.cpp | 460 + .../LinkProtoLib/XnSocketConnectionFactory.h | 78 + .../LinkProtoLib/XnSocketInConnection.cpp | 282 + .../LinkProtoLib/XnSocketInConnection.h | 55 + .../XnSyncServerSocketConnection.cpp | 234 + .../XnSyncServerSocketConnection.h | 102 + .../LinkProtoLib/XnSyncSocketConnection.cpp | 188 + .../LinkProtoLib/XnSyncSocketConnection.h | 50 + Source/Drivers/PSLink/Makefile | 39 + Source/Drivers/PSLink/PS1200Device.cpp | 222 + Source/Drivers/PSLink/PS1200Device.h | 53 + Source/Drivers/PSLink/PSLink.vcxproj | 302 + Source/Drivers/PSLink/PSLink.vcxproj.filters | 331 + Source/Drivers/PSLink/PSLinkConsole/Makefile | 36 + .../PSLink/PSLinkConsole/PSLinkConsole.cpp | 1672 ++ .../PSLinkConsole/PSLinkConsole.vcxproj | 191 + .../PSLinkConsole.vcxproj.filters | 18 + Source/Drivers/PSLink/PrimeClient.cpp | 644 + Source/Drivers/PSLink/PrimeClient.h | 127 + Source/Drivers/PSLink/PrimeClientDefs.h | 40 + .../PSLink/Protocols/XnLinkProto/XnLinkDefs.h | 457 + .../Protocols/XnLinkProto/XnLinkProto.h | 786 + Source/Drivers/PSLink/XnPsVersion.h | 36 + Source/Drivers/TestDevice/TestDevice.cpp | 420 + Source/Drivers/TestDevice/TestDevice.vcxproj | 179 + Source/Resources/OpenNI.rc | 123 + Source/Resources/Resource-OpenNI.h | 36 + Source/Resources/mainicon.ico | Bin 0 -> 10534 bytes Source/Tools/NiViewer/Audio.cpp | 249 + Source/Tools/NiViewer/Audio.h | 36 + Source/Tools/NiViewer/Capture.cpp | 466 + Source/Tools/NiViewer/Capture.h | 58 + Source/Tools/NiViewer/Device.cpp | 893 + Source/Tools/NiViewer/Device.h | 145 + Source/Tools/NiViewer/Draw.cpp | 1835 ++ Source/Tools/NiViewer/Draw.h | 108 + Source/Tools/NiViewer/Keyboard.cpp | 231 + Source/Tools/NiViewer/Keyboard.h | 61 + Source/Tools/NiViewer/Makefile | 35 + Source/Tools/NiViewer/Menu.cpp | 149 + Source/Tools/NiViewer/Menu.h | 38 + Source/Tools/NiViewer/MouseInput.cpp | 107 + Source/Tools/NiViewer/MouseInput.h | 70 + Source/Tools/NiViewer/NiViewer.cpp | 735 + Source/Tools/NiViewer/NiViewer.vcxproj | 201 + ThirdParty/Android.mk | 17 + ThirdParty/GL/glh/glh_array.h | 274 + ThirdParty/GL/glh/glh_convenience.h | 209 + ThirdParty/GL/glh/glh_cube_map.h | 347 + ThirdParty/GL/glh/glh_extensions.h | 297 + ThirdParty/GL/glh/glh_genext.h | 5379 +++++ ThirdParty/GL/glh/glh_glut.h | 861 + ThirdParty/GL/glh/glh_glut2.h | 667 + ThirdParty/GL/glh/glh_glut_callfunc.h | 85 + ThirdParty/GL/glh/glh_glut_replay.h | 292 + ThirdParty/GL/glh/glh_glut_text.h | 195 + ThirdParty/GL/glh/glh_interactors.h | 213 + ThirdParty/GL/glh/glh_linear.h | 1617 ++ ThirdParty/GL/glh/glh_mipmaps.h | 157 + ThirdParty/GL/glh/glh_obs.h | 636 + ThirdParty/GL/glh/glh_text.h | 197 + ThirdParty/GL/glut32.dll | Bin 0 -> 169984 bytes ThirdParty/GL/glut32.lib | Bin 0 -> 79898 bytes ThirdParty/GL/glut64.dll | Bin 0 -> 272896 bytes ThirdParty/GL/glut64.lib | Bin 0 -> 26180 bytes ThirdParty/LibJPEG/README | 385 + ThirdParty/LibJPEG/cderror.h | 132 + ThirdParty/LibJPEG/jcapimin.c | 280 + ThirdParty/LibJPEG/jcapistd.c | 161 + ThirdParty/LibJPEG/jccoefct.c | 449 + ThirdParty/LibJPEG/jccolor.c | 459 + ThirdParty/LibJPEG/jcdctmgr.c | 387 + ThirdParty/LibJPEG/jchuff.c | 909 + ThirdParty/LibJPEG/jchuff.h | 47 + ThirdParty/LibJPEG/jcinit.c | 72 + ThirdParty/LibJPEG/jcmainct.c | 293 + ThirdParty/LibJPEG/jcmarker.c | 664 + ThirdParty/LibJPEG/jcmaster.c | 590 + ThirdParty/LibJPEG/jcomapi.c | 106 + ThirdParty/LibJPEG/jconfig.h | 12 + ThirdParty/LibJPEG/jconfig.lnx86 | 45 + ThirdParty/LibJPEG/jconfig.ps3 | 45 + ThirdParty/LibJPEG/jconfig.vc | 47 + ThirdParty/LibJPEG/jcparam.c | 610 + ThirdParty/LibJPEG/jcphuff.c | 833 + ThirdParty/LibJPEG/jcprepct.c | 354 + ThirdParty/LibJPEG/jcsample.c | 519 + ThirdParty/LibJPEG/jctrans.c | 388 + ThirdParty/LibJPEG/jdapimin.c | 395 + ThirdParty/LibJPEG/jdapistd.c | 275 + ThirdParty/LibJPEG/jdatadst.c | 151 + ThirdParty/LibJPEG/jdatasrc.c | 212 + ThirdParty/LibJPEG/jdcoefct.c | 736 + ThirdParty/LibJPEG/jdcolor.c | 396 + ThirdParty/LibJPEG/jdct.h | 176 + ThirdParty/LibJPEG/jddctmgr.c | 269 + ThirdParty/LibJPEG/jdhuff.c | 651 + ThirdParty/LibJPEG/jdhuff.h | 201 + ThirdParty/LibJPEG/jdinput.c | 381 + ThirdParty/LibJPEG/jdmainct.c | 512 + ThirdParty/LibJPEG/jdmarker.c | 1360 ++ ThirdParty/LibJPEG/jdmaster.c | 557 + ThirdParty/LibJPEG/jdmerge.c | 400 + ThirdParty/LibJPEG/jdphuff.c | 668 + ThirdParty/LibJPEG/jdpostct.c | 290 + ThirdParty/LibJPEG/jdsample.c | 478 + ThirdParty/LibJPEG/jdtrans.c | 143 + ThirdParty/LibJPEG/jerror.c | 252 + ThirdParty/LibJPEG/jerror.h | 291 + ThirdParty/LibJPEG/jfdctflt.c | 168 + ThirdParty/LibJPEG/jfdctfst.c | 224 + ThirdParty/LibJPEG/jfdctint.c | 283 + ThirdParty/LibJPEG/jidctflt.c | 242 + ThirdParty/LibJPEG/jidctfst.c | 368 + ThirdParty/LibJPEG/jidctint.c | 389 + ThirdParty/LibJPEG/jidctred.c | 398 + ThirdParty/LibJPEG/jinclude.h | 92 + ThirdParty/LibJPEG/jmemmgr.c | 1119 + ThirdParty/LibJPEG/jmemnobs.c | 109 + ThirdParty/LibJPEG/jmemsys.h | 198 + ThirdParty/LibJPEG/jmorecfg.h | 366 + ThirdParty/LibJPEG/jpegint.h | 392 + ThirdParty/LibJPEG/jpeglib.h | 1105 + ThirdParty/LibJPEG/jquant1.c | 856 + ThirdParty/LibJPEG/jquant2.c | 1310 ++ ThirdParty/LibJPEG/jutils.c | 179 + ThirdParty/LibJPEG/jversion.h | 14 + ThirdParty/PSCommon/.gitignore | 18 + ThirdParty/PSCommon/Android.mk | 16 + .../PSCommon/BuildSystem/BuildJavaWindows.py | 115 + .../PSCommon/BuildSystem/CommonCSMakefile | 58 + .../PSCommon/BuildSystem/CommonCppMakefile | 144 + .../PSCommon/BuildSystem/CommonDefs.mak | 62 + .../PSCommon/BuildSystem/CommonJavaMakefile | 76 + .../PSCommon/BuildSystem/CommonTargets.mak | 23 + .../PSCommon/BuildSystem/OpenNICommon.props | 17 + ThirdParty/PSCommon/BuildSystem/Platform.Arm | 14 + ThirdParty/PSCommon/BuildSystem/Platform.x64 | 5 + ThirdParty/PSCommon/BuildSystem/Platform.x86 | 25 + .../PSCommon/RedistSystem/CopyToRepository.py | 98 + ThirdParty/PSCommon/RedistSystem/__init__.py | 20 + .../PSCommon/RedistSystem/redist_base.py | 925 + .../PSCommon/Testing/gmock-gtest-all.cc | 10554 +++++++++ ThirdParty/PSCommon/Testing/gmock/gmock.h | 12822 ++++++++++ ThirdParty/PSCommon/Testing/gmock_main.cc | 54 + ThirdParty/PSCommon/Testing/gtest/gtest.h | 19537 ++++++++++++++++ ThirdParty/PSCommon/XnLib/Android.mk | 16 + .../Win32/Bin/amd64/WdfCoInstaller01009.dll | Bin 0 -> 1721576 bytes .../XnLib/Driver/Win32/Bin/amd64/psdrv3.sys | Bin 0 -> 24968 bytes .../XnLib/Driver/Win32/Bin/dpinst-amd64.exe | Bin 0 -> 1050104 bytes .../XnLib/Driver/Win32/Bin/dpinst-x86.exe | Bin 0 -> 921992 bytes .../XnLib/Driver/Win32/Bin/dpinst.xml | 15 + .../XnLib/Driver/Win32/Bin/psdrv3.cat | Bin 0 -> 13173 bytes .../XnLib/Driver/Win32/Bin/psdrv3.ico | Bin 0 -> 10534 bytes .../Win32/Bin/x86/WdfCoInstaller01009.dll | Bin 0 -> 1461992 bytes .../XnLib/Driver/Win32/Bin/x86/psdrv3.sys | Bin 0 -> 21384 bytes .../Driver/Win32/Build/sys/PSDrvPublic.h | 156 + .../Android-Arm/XnPlatformAndroid-Arm.h | 58 + .../Include/Linux-Arm/XnPlatformLinux-Arm.h | 36 + .../XnLib/Include/Linux-x86/XnOSLinux-x86.h | 165 + .../Include/Linux-x86/XnPlatformLinux-x86.h | 195 + .../XnLib/Include/MacOSX/XnOSMacOSX.h | 30 + .../XnLib/Include/MacOSX/XnPlatformMacOSX.h | 42 + .../PSCommon/XnLib/Include/Win32/XnOSWin32.h | 152 + .../XnLib/Include/Win32/XnPlatformWin32.h | 257 + .../PSCommon/XnLib/Include/Win32/usb100.h | 290 + ThirdParty/PSCommon/XnLib/Include/XnArray.h | 322 + ThirdParty/PSCommon/XnLib/Include/XnBitSet.h | 131 + ThirdParty/PSCommon/XnLib/Include/XnBox3D.h | 77 + .../XnLib/Include/XnCallStackLogger.h | 183 + .../PSCommon/XnLib/Include/XnCallback.h | 40 + .../XnLib/Include/XnCriticalSection.h | 31 + .../PSCommon/XnLib/Include/XnCyclicStack.h | 100 + .../PSCommon/XnLib/Include/XnDataStructures.h | 39 + ThirdParty/PSCommon/XnLib/Include/XnDump.h | 201 + .../PSCommon/XnLib/Include/XnDumpWriters.h | 147 + .../PSCommon/XnLib/Include/XnErrorLogger.h | 73 + ThirdParty/PSCommon/XnLib/Include/XnEvent.h | 353 + .../PSCommon/XnLib/Include/XnFPSCalculator.h | 71 + ThirdParty/PSCommon/XnLib/Include/XnHash.h | 541 + ThirdParty/PSCommon/XnLib/Include/XnLib.h | 32 + ThirdParty/PSCommon/XnLib/Include/XnList.h | 333 + .../PSCommon/XnLib/Include/XnLockGuard.h | 66 + .../PSCommon/XnLib/Include/XnLockable.h | 245 + ThirdParty/PSCommon/XnLib/Include/XnLog.h | 542 + .../PSCommon/XnLib/Include/XnLogTypes.h | 78 + .../PSCommon/XnLib/Include/XnLogWriterBase.h | 127 + ThirdParty/PSCommon/XnLib/Include/XnMacros.h | 127 + ThirdParty/PSCommon/XnLib/Include/XnMath.h | 171 + .../PSCommon/XnLib/Include/XnMatrix3x3.h | 275 + ThirdParty/PSCommon/XnLib/Include/XnMemory.h | 46 + ThirdParty/PSCommon/XnLib/Include/XnOS.h | 670 + ThirdParty/PSCommon/XnLib/Include/XnOSCpp.h | 222 + .../PSCommon/XnLib/Include/XnOSStrings.h | 46 + ThirdParty/PSCommon/XnLib/Include/XnPair.h | 43 + .../PSCommon/XnLib/Include/XnPlatform.h | 107 + ThirdParty/PSCommon/XnLib/Include/XnPool.h | 133 + .../PSCommon/XnLib/Include/XnPriorityQueue.h | 104 + .../PSCommon/XnLib/Include/XnProfiling.h | 130 + .../PSCommon/XnLib/Include/XnProperty.h | 91 + .../PSCommon/XnLib/Include/XnQuaternion.h | 35 + ThirdParty/PSCommon/XnLib/Include/XnQueue.h | 80 + .../PSCommon/XnLib/Include/XnSIMD-Neon.h | 177 + .../PSCommon/XnLib/Include/XnSIMD-None.h | 482 + .../PSCommon/XnLib/Include/XnSIMD-SSE.h | 170 + ThirdParty/PSCommon/XnLib/Include/XnSIMD.h | 59 + .../PSCommon/XnLib/Include/XnScheduler.h | 91 + .../PSCommon/XnLib/Include/XnSmartPointer.h | 127 + ThirdParty/PSCommon/XnLib/Include/XnStatus.h | 106 + .../PSCommon/XnLib/Include/XnStatusCodes.h | 220 + .../PSCommon/XnLib/Include/XnStatusRegister.h | 80 + ThirdParty/PSCommon/XnLib/Include/XnString.h | 164 + .../PSCommon/XnLib/Include/XnStringsHash.h | 110 + .../XnLib/Include/XnSymmetricMatrix3x3.h | 278 + .../XnLib/Include/XnThreadSafeQueue.h | 79 + ThirdParty/PSCommon/XnLib/Include/XnUSB.h | 127 + .../PSCommon/XnLib/Include/XnUSBDevice.h | 117 + .../PSCommon/XnLib/Include/XnVector3D.h | 325 + ThirdParty/PSCommon/XnLib/Source/Android.mk | 46 + .../Source/Linux/XnLinuxCriticalSections.cpp | 47 + .../XnLib/Source/Linux/XnLinuxDebug.cpp | 78 + .../XnLib/Source/Linux/XnLinuxEvents.cpp | 158 + .../XnLib/Source/Linux/XnLinuxEvents.h | 80 + .../XnLib/Source/Linux/XnLinuxFiles.cpp | 687 + .../XnLib/Source/Linux/XnLinuxINI.cpp | 268 + .../XnLib/Source/Linux/XnLinuxKeyboard.cpp | 75 + .../XnLib/Source/Linux/XnLinuxMemory.cpp | 162 + .../XnLib/Source/Linux/XnLinuxMutex.cpp | 416 + .../XnLib/Source/Linux/XnLinuxNetwork.cpp | 515 + .../XnLib/Source/Linux/XnLinuxPosixEvents.cpp | 154 + .../XnLib/Source/Linux/XnLinuxPosixEvents.h | 38 + .../Source/Linux/XnLinuxPosixNamedEvents.cpp | 89 + .../Source/Linux/XnLinuxPosixNamedEvents.h | 53 + .../XnLib/Source/Linux/XnLinuxProcesses.cpp | 151 + .../XnLib/Source/Linux/XnLinuxSharedLibs.cpp | 112 + .../Source/Linux/XnLinuxSharedMemory.cpp | 273 + .../XnLib/Source/Linux/XnLinuxStrings.cpp | 353 + .../Source/Linux/XnLinuxSysVNamedEvents.cpp | 265 + .../Source/Linux/XnLinuxSysVNamedEvents.h | 56 + .../XnLib/Source/Linux/XnLinuxThreads.cpp | 204 + .../XnLib/Source/Linux/XnLinuxTime.cpp | 204 + .../XnLib/Source/Linux/XnLinuxUSB.cpp | 1728 ++ .../PSCommon/XnLib/Source/Linux/XnLinuxUSB.h | 97 + .../XnLib/Source/Linux/XnLinuxUSBDevice.cpp | 1151 + ThirdParty/PSCommon/XnLib/Source/Makefile | 25 + .../Source/PS3/XnPS3CriticalSections.cpp | 48 + .../PSCommon/XnLib/Source/PS3/XnPS3Events.cpp | 123 + .../PSCommon/XnLib/Source/PS3/XnPS3Files.cpp | 198 + .../PSCommon/XnLib/Source/PS3/XnPS3INI.cpp | 68 + .../PSCommon/XnLib/Source/PS3/XnPS3Memory.cpp | 130 + .../PSCommon/XnLib/Source/PS3/XnPS3Mutex.cpp | 102 + .../XnLib/Source/PS3/XnPS3Network.cpp | 105 + .../XnLib/Source/PS3/XnPS3SharedLibs.cpp | 43 + .../XnLib/Source/PS3/XnPS3Strings.cpp | 312 + .../XnLib/Source/PS3/XnPS3Threads.cpp | 62 + .../PSCommon/XnLib/Source/PS3/XnPS3Time.cpp | 130 + .../PSCommon/XnLib/Source/PS3/XnPS3USB.cpp | 1656 ++ .../PSCommon/XnLib/Source/PS3/XnPS3USB.h | 132 + .../XnLib/Source/Win32/XnUSBWin32.cpp | 1672 ++ .../PSCommon/XnLib/Source/Win32/XnUSBWin32.h | 106 + .../Source/Win32/XnWin32CriticalSection.cpp | 87 + .../XnLib/Source/Win32/XnWin32Debug.cpp | 240 + .../XnLib/Source/Win32/XnWin32Events.cpp | 210 + .../XnLib/Source/Win32/XnWin32Files.cpp | 755 + .../XnLib/Source/Win32/XnWin32INI.cpp | 277 + .../XnLib/Source/Win32/XnWin32Internal.h | 24 + .../XnLib/Source/Win32/XnWin32Keyboard.cpp | 36 + .../XnLib/Source/Win32/XnWin32Memory.cpp | 139 + .../XnLib/Source/Win32/XnWin32Mutex.cpp | 165 + .../XnLib/Source/Win32/XnWin32Network.cpp | 518 + .../PSCommon/XnLib/Source/Win32/XnWin32OS.cpp | 302 + .../XnLib/Source/Win32/XnWin32Processes.cpp | 77 + .../XnLib/Source/Win32/XnWin32Semaphore.cpp | 88 + .../Source/Win32/XnWin32SharedLibrary.cpp | 105 + .../Source/Win32/XnWin32SharedMemory.cpp | 238 + .../XnLib/Source/Win32/XnWin32Strings.cpp | 344 + .../XnLib/Source/Win32/XnWin32Threads.cpp | 179 + .../XnLib/Source/Win32/XnWin32Time.cpp | 169 + .../PSCommon/XnLib/Source/Win32/devioctl.h | 184 + ThirdParty/PSCommon/XnLib/Source/Win32/usb.h | 1103 + .../PSCommon/XnLib/Source/Win32/usb100.h | 290 + .../PSCommon/XnLib/Source/Win32/usb200.h | 146 + ThirdParty/PSCommon/XnLib/Source/XnDump.cpp | 395 + .../XnLib/Source/XnDumpFileWriter.cpp | 68 + .../PSCommon/XnLib/Source/XnDumpFileWriter.h | 40 + ThirdParty/PSCommon/XnLib/Source/XnEnum.h | 116 + .../PSCommon/XnLib/Source/XnErrorLogger.cpp | 148 + .../PSCommon/XnLib/Source/XnFPSCalculator.cpp | 131 + ThirdParty/PSCommon/XnLib/Source/XnFiles.cpp | 141 + ThirdParty/PSCommon/XnLib/Source/XnLib.cpp | 22 + .../PSCommon/XnLib/Source/XnLib.vcxproj | 275 + .../XnLib/Source/XnLib.vcxproj.filters | 285 + ThirdParty/PSCommon/XnLib/Source/XnLog.cpp | 977 + .../XnLib/Source/XnLogAndroidWriter.cpp | 68 + .../XnLib/Source/XnLogAndroidWriter.h | 36 + .../XnLib/Source/XnLogConsoleWriter.cpp | 37 + .../XnLib/Source/XnLogConsoleWriter.h | 36 + .../PSCommon/XnLib/Source/XnLogFileWriter.cpp | 121 + .../PSCommon/XnLib/Source/XnLogFileWriter.h | 58 + ThirdParty/PSCommon/XnLib/Source/XnOS.cpp | 75 + .../XnLib/Source/XnOSMemoryProfiling.cpp | 272 + .../PSCommon/XnLib/Source/XnProfiling.cpp | 267 + .../PSCommon/XnLib/Source/XnScheduler.cpp | 384 + ThirdParty/PSCommon/XnLib/Source/XnStatus.cpp | 148 + .../PSCommon/XnLib/Source/XnStrings.cpp | 47 + .../XnLib/Source/XnSytmmetricMatrix3x3.cpp | 211 + .../PSCommon/XnLib/Source/XnThreads.cpp | 46 + ThirdParty/PSCommon/XnLib/Source/XnUSB.cpp | 69 + .../PSCommon/XnLib/Source/XnUSBInternal.h | 83 + .../PSCommon/XnLib/Source/XnVector3D.cpp | 38 + .../PSCommon/XnLib/ThirdParty/Android.mk | 16 + .../XnLib/ThirdParty/GL/glh/glh_array.h | 274 + .../XnLib/ThirdParty/GL/glh/glh_convenience.h | 209 + .../XnLib/ThirdParty/GL/glh/glh_cube_map.h | 347 + .../XnLib/ThirdParty/GL/glh/glh_extensions.h | 297 + .../XnLib/ThirdParty/GL/glh/glh_genext.h | 5379 +++++ .../XnLib/ThirdParty/GL/glh/glh_glut.h | 861 + .../XnLib/ThirdParty/GL/glh/glh_glut2.h | 667 + .../ThirdParty/GL/glh/glh_glut_callfunc.h | 85 + .../XnLib/ThirdParty/GL/glh/glh_glut_replay.h | 292 + .../XnLib/ThirdParty/GL/glh/glh_glut_text.h | 195 + .../XnLib/ThirdParty/GL/glh/glh_interactors.h | 213 + .../XnLib/ThirdParty/GL/glh/glh_linear.h | 1617 ++ .../XnLib/ThirdParty/GL/glh/glh_mipmaps.h | 157 + .../XnLib/ThirdParty/GL/glh/glh_obs.h | 636 + .../XnLib/ThirdParty/GL/glh/glh_text.h | 197 + .../PSCommon/XnLib/ThirdParty/GL/glut32.dll | Bin 0 -> 169984 bytes .../PSCommon/XnLib/ThirdParty/GL/glut32.lib | Bin 0 -> 79898 bytes .../PSCommon/XnLib/ThirdParty/GL/glut64.dll | Bin 0 -> 272896 bytes .../PSCommon/XnLib/ThirdParty/GL/glut64.lib | Bin 0 -> 26180 bytes .../ThirdParty/libusb-1.0.9-Android/AUTHORS | 45 + .../libusb-1.0.9-Android/Android.mk | 52 + .../ThirdParty/libusb-1.0.9-Android/COPYING | 504 + .../ThirdParty/libusb-1.0.9-Android/ChangeLog | 4377 ++++ .../ThirdParty/libusb-1.0.9-Android/INSTALL | 234 + .../libusb-1.0.9-Android/Makefile.am | 28 + .../libusb-1.0.9-Android/Makefile.in | 807 + .../ThirdParty/libusb-1.0.9-Android/NEWS | 65 + .../ThirdParty/libusb-1.0.9-Android/PORTING | 95 + .../ThirdParty/libusb-1.0.9-Android/README | 22 + .../ThirdParty/libusb-1.0.9-Android/THANKS | 8 + .../ThirdParty/libusb-1.0.9-Android/TODO | 9 + .../libusb-1.0.9-Android/aclocal.m4 | 9111 +++++++ .../ThirdParty/libusb-1.0.9-Android/compile | 143 + .../libusb-1.0.9-Android/config.guess | 1523 ++ .../ThirdParty/libusb-1.0.9-Android/config.h | 119 + .../libusb-1.0.9-Android/config.h.in | 116 + .../libusb-1.0.9-Android/config.sub | 1757 ++ .../ThirdParty/libusb-1.0.9-Android/configure | 14148 +++++++++++ .../libusb-1.0.9-Android/configure.ac | 222 + .../ThirdParty/libusb-1.0.9-Android/depcomp | 630 + .../libusb-1.0.9-Android/doc/Makefile.am | 10 + .../libusb-1.0.9-Android/doc/Makefile.in | 377 + .../libusb-1.0.9-Android/doc/doxygen.cfg.in | 1294 + .../libusb-1.0.9-Android/examples/Makefile.am | 21 + .../libusb-1.0.9-Android/examples/Makefile.in | 542 + .../libusb-1.0.9-Android/examples/dpfp.c | 507 + .../examples/dpfp_threaded.c | 545 + .../libusb-1.0.9-Android/examples/listdevs.c | 64 + .../libusb-1.0.9-Android/install-sh | 520 + .../libusb-1.0.9-Android/libusb-1.0.pc.in | 12 + .../libusb-1.0.9-Android/libusb/Makefile.am | 49 + .../libusb-1.0.9-Android/libusb/Makefile.in | 718 + .../libusb-1.0.9-Android/libusb/core.c | 1875 ++ .../libusb-1.0.9-Android/libusb/descriptor.c | 730 + .../libusb-1.0.9-Android/libusb/io.c | 2464 ++ .../libusb/libusb-1.0.def | 120 + .../libusb-1.0.9-Android/libusb/libusb-1.0.rc | 56 + .../libusb-1.0.9-Android/libusb/libusb.h | 1443 ++ .../libusb-1.0.9-Android/libusb/libusbi.h | 935 + .../libusb/os/darwin_usb.c | 1750 ++ .../libusb/os/darwin_usb.h | 169 + .../libusb/os/linux_usbfs.c | 2486 ++ .../libusb/os/linux_usbfs.h | 139 + .../libusb/os/openbsd_usb.c | 727 + .../libusb/os/poll_posix.h | 10 + .../libusb/os/poll_windows.c | 745 + .../libusb/os/poll_windows.h | 115 + .../libusb/os/threads_posix.c | 55 + .../libusb/os/threads_posix.h | 48 + .../libusb/os/threads_windows.c | 207 + .../libusb/os/threads_windows.h | 86 + .../libusb/os/windows_usb.c | 2996 +++ .../libusb/os/windows_usb.h | 608 + .../libusb-1.0.9-Android/libusb/sync.c | 314 + .../libusb-1.0.9-Android/libusb/version.h | 18 + .../ThirdParty/libusb-1.0.9-Android/ltmain.sh | 8745 +++++++ .../ThirdParty/libusb-1.0.9-Android/missing | 376 + .../libusb-1.0.9-Android/msvc/config.h | 24 + .../libusb-1.0.9-Android/msvc/ddk_build.cmd | 106 + .../libusb-1.0.9-Android/msvc/inttypes.h | 295 + .../libusb-1.0.9-Android/msvc/libusb.dsw | 56 + .../libusb-1.0.9-Android/msvc/libusb_dll.dsp | 190 + .../msvc/libusb_dll.vcproj | 421 + .../msvc/libusb_dll.vcxproj | 166 + .../msvc/libusb_dll.vcxproj.filters | 69 + .../libusb-1.0.9-Android/msvc/libusb_sources | 36 + .../msvc/libusb_static.dsp | 170 + .../msvc/libusb_static.vcproj | 346 + .../msvc/libusb_static.vcxproj | 150 + .../msvc/libusb_static.vcxproj.filters | 56 + .../msvc/libusb_vs2005.sln | 61 + .../msvc/libusb_vs2010.sln | 49 + .../libusb-1.0.9-Android/msvc/listdevs.dsp | 103 + .../libusb-1.0.9-Android/msvc/listdevs.vcproj | 360 + .../msvc/listdevs.vcxproj | 165 + .../msvc/listdevs.vcxproj.filters | 14 + .../msvc/listdevs_sources | 19 + .../libusb-1.0.9-Android/msvc/stdint.h | 256 + ThirdParty/PSCommon/XnLib/XnLib.sln | 19 + Wrappers/java/OpenNI.java/Build.bat | 1 + Wrappers/java/OpenNI.java/Makefile | 11 + .../src/org/openni/CoordinateConverter.java | 186 + .../OpenNI.java/src/org/openni/CropArea.java | 59 + .../OpenNI.java/src/org/openni/Device.java | 218 + .../src/org/openni/DeviceInfo.java | 75 + .../src/org/openni/ImageRegistrationMode.java | 30 + .../src/org/openni/NativeMethods.java | 192 + .../OpenNI.java/src/org/openni/OpenNI.java | 261 + .../OpenNI.java/src/org/openni/OutArg.java | 11 + .../src/org/openni/PixelFormat.java | 35 + .../src/org/openni/PlaybackControl.java | 113 + .../OpenNI.java/src/org/openni/Point2D.java | 37 + .../OpenNI.java/src/org/openni/Point3D.java | 48 + .../OpenNI.java/src/org/openni/Recorder.java | 83 + .../src/org/openni/SensorInfo.java | 57 + .../src/org/openni/SensorType.java | 31 + .../OpenNI.java/src/org/openni/Version.java | 59 + .../src/org/openni/VideoFrameRef.java | 181 + .../OpenNI.java/src/org/openni/VideoMode.java | 116 + .../src/org/openni/VideoStream.java | 1 + Wrappers/java/OpenNI.jni/CreateMethods.py | 51 + Wrappers/java/OpenNI.jni/Makefile | 18 + Wrappers/java/OpenNI.jni/OpenNI.jni.cpp | 111 + Wrappers/java/OpenNI.jni/OpenNI.jni.sln | 26 + Wrappers/java/OpenNI.jni/OpenNI.jni.vcxproj | 150 + Wrappers/java/OpenNI.jni/UpdateHeaders.bat | 3 + Wrappers/java/OpenNI.jni/UpdateHeaders.sh | 3 + Wrappers/java/OpenNI.jni/jni.h | 1964 ++ Wrappers/java/OpenNI.jni/jni_md.h | 34 + Wrappers/java/OpenNI.jni/methods.inl | 57 + .../OpenNI.jni/org_openni_NativeMethods.cpp | 752 + .../OpenNI.jni/org_openni_NativeMethods.h | 507 + 952 files changed, 303566 insertions(+) create mode 100644 .gitignore create mode 100644 Android.mk create mode 100644 Application.mk create mode 100644 CHANGES.txt create mode 100644 Config/OpenNI.ini create mode 100644 Config/OpenNI2/Drivers/PS1080.ini create mode 100644 Config/OpenNI2/Drivers/PSLink.ini create mode 100644 Include/Android-Arm/OniPlatformAndroid-Arm.h create mode 100644 Include/Driver/OniDriverAPI.h create mode 100644 Include/Driver/OniDriverTypes.h create mode 100644 Include/Linux-Arm/OniPlatformLinux-Arm.h create mode 100644 Include/Linux-x86/OniPlatformLinux-x86.h create mode 100644 Include/MacOSX/OniPlatformMacOSX.h create mode 100644 Include/OniCAPI.h create mode 100644 Include/OniCEnums.h create mode 100644 Include/OniCProperties.h create mode 100644 Include/OniCTypes.h create mode 100644 Include/OniEnums.h create mode 100644 Include/OniPlatform.h create mode 100644 Include/OniProperties.h create mode 100644 Include/OniVersion.h create mode 100644 Include/OpenNI.h create mode 100644 Include/PS1080.h create mode 100644 Include/PSLink.h create mode 100644 Include/PrimeSense.h create mode 100644 Include/Win32/OniPlatformWin32.h create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 NOTICE create mode 100644 OpenNI.sln create mode 100755 Packaging/Harvest.py create mode 100644 Packaging/Install/Fragments/.gitignore create mode 100644 Packaging/Install/Includes/Variables.wxi create mode 100644 Packaging/Install/Install.wixproj create mode 100644 Packaging/Install/Install.wxs create mode 100644 Packaging/Install/Lang/en-us/Loc_en-us.wxl create mode 100644 Packaging/Install/Resources/Microsoft_VC100_CRT_x64.msm create mode 100644 Packaging/Install/Resources/Microsoft_VC100_CRT_x86.msm create mode 100644 Packaging/Install/Resources/Microsoft_VC90_CRT_x86.msm create mode 100644 Packaging/Install/Resources/Microsoft_VC90_CRT_x86_x64.msm create mode 100644 Packaging/Install/Resources/OpenNIBackground.bmp create mode 100644 Packaging/Install/Resources/OpenNIHeader.bmp create mode 100644 Packaging/Install/Resources/mainicon.ico create mode 100644 Packaging/Install/UI/CustomeInstallUI.wxs create mode 100644 Packaging/Install/setup.exe create mode 100755 Packaging/Linux/install.sh create mode 100644 Packaging/Linux/primesense-usb.rules create mode 100755 Packaging/ReleaseVersion.py create mode 100755 Packaging/UpdateVersion.py create mode 100644 README create mode 100644 ReleaseNotes.txt create mode 100644 Samples/Android.mk create mode 100644 Samples/ClosestPointViewer/ClosestPointViewer.vcxproj create mode 100644 Samples/ClosestPointViewer/Makefile create mode 100644 Samples/ClosestPointViewer/Viewer.cpp create mode 100644 Samples/ClosestPointViewer/Viewer.h create mode 100644 Samples/ClosestPointViewer/main.cpp create mode 100644 Samples/Common/OniSampleUtilities.h create mode 100644 Samples/EventBasedRead/Android.mk create mode 100644 Samples/EventBasedRead/EventBasedRead.vcxproj create mode 100644 Samples/EventBasedRead/Makefile create mode 100644 Samples/EventBasedRead/main.cpp create mode 100644 Samples/MWClosestPoint/MWClosestPoint.cpp create mode 100644 Samples/MWClosestPoint/MWClosestPoint.h create mode 100644 Samples/MWClosestPoint/MWClosestPoint.vcxproj create mode 100644 Samples/MWClosestPoint/MWClosestPoint.vcxproj.filters create mode 100644 Samples/MWClosestPoint/Makefile create mode 100644 Samples/MWClosestPointApp/MWClosestPointApp.vcxproj create mode 100644 Samples/MWClosestPointApp/MWClosestPointApp.vcxproj.filters create mode 100644 Samples/MWClosestPointApp/Makefile create mode 100644 Samples/MWClosestPointApp/main.cpp create mode 100644 Samples/MultiDepthViewer/Makefile create mode 100644 Samples/MultiDepthViewer/MultiDepthViewer.vcxproj create mode 100644 Samples/MultiDepthViewer/Viewer.cpp create mode 100644 Samples/MultiDepthViewer/Viewer.h create mode 100644 Samples/MultiDepthViewer/main.cpp create mode 100644 Samples/MultipleStreamRead/Android.mk create mode 100644 Samples/MultipleStreamRead/Makefile create mode 100644 Samples/MultipleStreamRead/MultipleStreamRead.vcxproj create mode 100644 Samples/MultipleStreamRead/main.cpp create mode 100644 Samples/SimpleRead/Android.mk create mode 100644 Samples/SimpleRead/Makefile create mode 100644 Samples/SimpleRead/SimpleRead.vcxproj create mode 100644 Samples/SimpleRead/main.cpp create mode 100644 Samples/SimpleViewer.java/Build.bat create mode 100644 Samples/SimpleViewer.java/Makefile create mode 100644 Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewer.java create mode 100755 Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewerApplication.java create mode 100644 Samples/SimpleViewer/Makefile create mode 100644 Samples/SimpleViewer/SimpleViewer.vcxproj create mode 100644 Samples/SimpleViewer/Viewer.cpp create mode 100644 Samples/SimpleViewer/Viewer.h create mode 100644 Samples/SimpleViewer/main.cpp create mode 100644 Source/Android.mk create mode 100644 Source/Core/Android.mk create mode 100644 Source/Core/Makefile create mode 100644 Source/Core/OniCommon.h create mode 100644 Source/Core/OniContext.cpp create mode 100644 Source/Core/OniContext.h create mode 100644 Source/Core/OniDataRecords.cpp create mode 100644 Source/Core/OniDataRecords.h create mode 100644 Source/Core/OniDebug.h create mode 100644 Source/Core/OniDevice.cpp create mode 100644 Source/Core/OniDevice.h create mode 100644 Source/Core/OniDeviceDriver.cpp create mode 100644 Source/Core/OniDeviceDriver.h create mode 100644 Source/Core/OniDriverHandler.cpp create mode 100644 Source/Core/OniDriverHandler.h create mode 100644 Source/Core/OniDriverServices.h create mode 100644 Source/Core/OniFrameHolder.h create mode 100644 Source/Core/OniFrameManager.cpp create mode 100644 Source/Core/OniFrameManager.h create mode 100644 Source/Core/OniInternal.h create mode 100644 Source/Core/OniRecorder.cpp create mode 100644 Source/Core/OniRecorder.h create mode 100755 Source/Core/OniSensor.cpp create mode 100644 Source/Core/OniSensor.h create mode 100644 Source/Core/OniStream.cpp create mode 100644 Source/Core/OniStream.h create mode 100644 Source/Core/OniStreamFrameHolder.cpp create mode 100644 Source/Core/OniStreamFrameHolder.h create mode 100644 Source/Core/OniSyncedStreamsFrameHolder.cpp create mode 100644 Source/Core/OniSyncedStreamsFrameHolder.h create mode 100644 Source/Core/OpenNI.cpp create mode 100644 Source/Core/OpenNI.vcxproj create mode 100644 Source/Core/OpenNI.vcxproj.filters create mode 100644 Source/Core/README.txt create mode 100644 Source/DepthUtils/Android.mk create mode 100644 Source/DepthUtils/DepthUtils.cpp create mode 100644 Source/DepthUtils/DepthUtils.h create mode 100644 Source/DepthUtils/DepthUtils.vcxproj create mode 100644 Source/DepthUtils/DepthUtilsImpl.cpp create mode 100644 Source/DepthUtils/DepthUtilsImpl.h create mode 100644 Source/DepthUtils/Makefile create mode 100644 Source/Documentation/Doxyfile create mode 100644 Source/Documentation/OpenNILogo.bmp create mode 100755 Source/Documentation/Runme.py create mode 100644 Source/Documentation/Text/Conventions.txt create mode 100644 Source/Documentation/Text/GettingStarted.txt create mode 100644 Source/Documentation/Text/MainPage.txt create mode 100644 Source/Drivers/Android.mk create mode 100644 Source/Drivers/DummyDevice/DummyDevice.cpp create mode 100644 Source/Drivers/DummyDevice/DummyDevice.vcxproj create mode 100644 Source/Drivers/DummyDevice/Makefile create mode 100644 Source/Drivers/Kinect/BaseKinectStream.cpp create mode 100644 Source/Drivers/Kinect/BaseKinectStream.h create mode 100644 Source/Drivers/Kinect/ColorKinectStream.cpp create mode 100644 Source/Drivers/Kinect/ColorKinectStream.h create mode 100644 Source/Drivers/Kinect/D2S.h.h create mode 100644 Source/Drivers/Kinect/DepthKinectStream.cpp create mode 100644 Source/Drivers/Kinect/DepthKinectStream.h create mode 100644 Source/Drivers/Kinect/IRKinectStream.cpp create mode 100644 Source/Drivers/Kinect/IRKinectStream.h create mode 100644 Source/Drivers/Kinect/Kinect.vcxproj create mode 100644 Source/Drivers/Kinect/Kinect.vcxproj.filters create mode 100644 Source/Drivers/Kinect/KinectDevice.cpp create mode 100644 Source/Drivers/Kinect/KinectDevice.h create mode 100644 Source/Drivers/Kinect/KinectDriver.cpp create mode 100644 Source/Drivers/Kinect/KinectDriver.h create mode 100644 Source/Drivers/Kinect/KinectStreamImpl.cpp create mode 100644 Source/Drivers/Kinect/KinectStreamImpl.h create mode 100644 Source/Drivers/Kinect/S2D.h.h create mode 100644 Source/Drivers/OniFile/Android.mk create mode 100644 Source/Drivers/OniFile/DataRecords.cpp create mode 100644 Source/Drivers/OniFile/DataRecords.h create mode 100644 Source/Drivers/OniFile/Formats/Xn16zCodec.h create mode 100644 Source/Drivers/OniFile/Formats/Xn16zEmbTablesCodec.h create mode 100644 Source/Drivers/OniFile/Formats/Xn8zCodec.h create mode 100644 Source/Drivers/OniFile/Formats/XnCodec.cpp create mode 100644 Source/Drivers/OniFile/Formats/XnCodec.h create mode 100644 Source/Drivers/OniFile/Formats/XnCodecBase.h create mode 100644 Source/Drivers/OniFile/Formats/XnCodecIDs.h create mode 100644 Source/Drivers/OniFile/Formats/XnJpegCodec.h create mode 100644 Source/Drivers/OniFile/Formats/XnStreamCompression.cpp create mode 100644 Source/Drivers/OniFile/Formats/XnStreamCompression.h create mode 100644 Source/Drivers/OniFile/Formats/XnStreamFormats.h create mode 100644 Source/Drivers/OniFile/Formats/XnUncompressedCodec.h create mode 100644 Source/Drivers/OniFile/Makefile create mode 100644 Source/Drivers/OniFile/OniFile.vcxproj create mode 100644 Source/Drivers/OniFile/OniFile.vcxproj.filters create mode 100644 Source/Drivers/OniFile/PlayerCodecFactory.cpp create mode 100644 Source/Drivers/OniFile/PlayerCodecFactory.h create mode 100644 Source/Drivers/OniFile/PlayerDevice.cpp create mode 100644 Source/Drivers/OniFile/PlayerDevice.h create mode 100644 Source/Drivers/OniFile/PlayerDriver.cpp create mode 100644 Source/Drivers/OniFile/PlayerDriver.h create mode 100644 Source/Drivers/OniFile/PlayerNode.cpp create mode 100644 Source/Drivers/OniFile/PlayerNode.h create mode 100644 Source/Drivers/OniFile/PlayerProperties.h create mode 100644 Source/Drivers/OniFile/PlayerSource.cpp create mode 100644 Source/Drivers/OniFile/PlayerSource.h create mode 100644 Source/Drivers/OniFile/PlayerStream.cpp create mode 100644 Source/Drivers/OniFile/PlayerStream.h create mode 100644 Source/Drivers/OniFile/XnPlayerTypes.h create mode 100644 Source/Drivers/OniFile/XnPropNames.h create mode 100644 Source/Drivers/PS1080/Android.mk create mode 100644 Source/Drivers/PS1080/Core/XnBuffer.cpp create mode 100644 Source/Drivers/PS1080/Core/XnBuffer.h create mode 100644 Source/Drivers/PS1080/Core/XnCore.cpp create mode 100644 Source/Drivers/PS1080/Core/XnCoreGlobals.h create mode 100644 Source/Drivers/PS1080/Core/XnCoreStatus.cpp create mode 100644 Source/Drivers/PS1080/Core/XnIOFileStream.cpp create mode 100644 Source/Drivers/PS1080/Core/XnIONetworkStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnActualGeneralProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnActualGeneralProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnActualIntProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnActualIntProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnActualPropertiesHash.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnActualPropertiesHash.h create mode 100644 Source/Drivers/PS1080/DDK/XnActualRealProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnActualRealProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnActualStringProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnActualStringProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnAudioStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnAudioStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnCodecFactory.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnCodecFactory.h create mode 100644 Source/Drivers/PS1080/DDK/XnDDK.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnDDKStatus.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnDepthStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnDepthStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceBase.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceBase.h create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceModule.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceModule.h create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.h create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnDeviceStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnFrameBufferManager.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnFrameBufferManager.h create mode 100644 Source/Drivers/PS1080/DDK/XnFrameStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnFrameStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnGeneralProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnGeneralProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnIRStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnIRStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnImageStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnImageStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnIntProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnIntProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.h create mode 100644 Source/Drivers/PS1080/DDK/XnPixelStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnPixelStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnPropertySet.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnPropertySetInternal.h create mode 100644 Source/Drivers/PS1080/DDK/XnRealProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnRealProperty.h create mode 100644 Source/Drivers/PS1080/DDK/XnShiftToDepth.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnShiftToDepth.h create mode 100644 Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.h create mode 100644 Source/Drivers/PS1080/DDK/XnStreamingStream.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnStreamingStream.h create mode 100644 Source/Drivers/PS1080/DDK/XnStringProperty.cpp create mode 100644 Source/Drivers/PS1080/DDK/XnStringProperty.h create mode 100644 Source/Drivers/PS1080/DriverImpl/XnExportedOniDriver.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniColorStream.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniColorStream.h create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniDepthStream.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniDepthStream.h create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniDevice.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniDevice.h create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniDriver.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniDriver.h create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniIRStream.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniIRStream.h create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniMapStream.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniMapStream.h create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniStream.cpp create mode 100644 Source/Drivers/PS1080/DriverImpl/XnOniStream.h create mode 100644 Source/Drivers/PS1080/Formats/Xn16zCodec.h create mode 100644 Source/Drivers/PS1080/Formats/Xn16zEmbTablesCodec.h create mode 100644 Source/Drivers/PS1080/Formats/Xn8zCodec.h create mode 100644 Source/Drivers/PS1080/Formats/XnCodec.h create mode 100644 Source/Drivers/PS1080/Formats/XnCodecBase.h create mode 100644 Source/Drivers/PS1080/Formats/XnCodecs.cpp create mode 100644 Source/Drivers/PS1080/Formats/XnFormats.cpp create mode 100644 Source/Drivers/PS1080/Formats/XnFormats.h create mode 100644 Source/Drivers/PS1080/Formats/XnFormatsMirror.cpp create mode 100644 Source/Drivers/PS1080/Formats/XnFormatsStatus.cpp create mode 100644 Source/Drivers/PS1080/Formats/XnJpegCodec.h create mode 100644 Source/Drivers/PS1080/Formats/XnNiCodec.h create mode 100644 Source/Drivers/PS1080/Formats/XnStreamCompression.cpp create mode 100644 Source/Drivers/PS1080/Formats/XnStreamCompression.h create mode 100644 Source/Drivers/PS1080/Formats/XnUncompressedCodec.h create mode 100644 Source/Drivers/PS1080/Include/XnCommon.h create mode 100644 Source/Drivers/PS1080/Include/XnCore.h create mode 100644 Source/Drivers/PS1080/Include/XnDDK.h create mode 100644 Source/Drivers/PS1080/Include/XnDDKStatus.h create mode 100644 Source/Drivers/PS1080/Include/XnDevice.h create mode 100644 Source/Drivers/PS1080/Include/XnDeviceProto.inl create mode 100644 Source/Drivers/PS1080/Include/XnDeviceProxy.h create mode 100644 Source/Drivers/PS1080/Include/XnFormatsStatus.h create mode 100644 Source/Drivers/PS1080/Include/XnIOFileStream.h create mode 100644 Source/Drivers/PS1080/Include/XnIONetworkStream.h create mode 100644 Source/Drivers/PS1080/Include/XnIOStream.h create mode 100644 Source/Drivers/PS1080/Include/XnPlatformBC.h create mode 100644 Source/Drivers/PS1080/Include/XnPropertySet.h create mode 100644 Source/Drivers/PS1080/Include/XnPsVersion.h create mode 100644 Source/Drivers/PS1080/Include/XnStreamFormats.h create mode 100644 Source/Drivers/PS1080/Include/XnStreamParams.h create mode 100644 Source/Drivers/PS1080/Makefile create mode 100644 Source/Drivers/PS1080/PS1080.vcxproj create mode 100644 Source/Drivers/PS1080/PS1080.vcxproj.filters create mode 100644 Source/Drivers/PS1080/PS1080Console/Makefile create mode 100644 Source/Drivers/PS1080/PS1080Console/PS1080Console.cpp create mode 100644 Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj create mode 100644 Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj.filters create mode 100644 Source/Drivers/PS1080/Sensor/Bayer.cpp create mode 100644 Source/Drivers/PS1080/Sensor/Bayer.h create mode 100644 Source/Drivers/PS1080/Sensor/IXnSensorStream.h create mode 100644 Source/Drivers/PS1080/Sensor/Uncomp.cpp create mode 100644 Source/Drivers/PS1080/Sensor/Uncomp.h create mode 100644 Source/Drivers/PS1080/Sensor/XnAudioProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnAudioProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnCmosInfo.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnCmosInfo.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDataProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnDataProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDepthProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnDepthProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceSensor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.h create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.h create mode 100644 Source/Drivers/PS1080/Sensor/XnFirmwareCommands.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnFirmwareCommands.h create mode 100644 Source/Drivers/PS1080/Sensor/XnFirmwareInfo.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnFirmwareInfo.h create mode 100644 Source/Drivers/PS1080/Sensor/XnFirmwareStreams.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnFirmwareStreams.h create mode 100644 Source/Drivers/PS1080/Sensor/XnFirmwareTypes.h create mode 100644 Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnHostProtocol.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnHostProtocol.h create mode 100644 Source/Drivers/PS1080/Sensor/XnIRProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnIRProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnParams.h create mode 100644 Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorAudioStream.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorAudioStream.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorDepthStream.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorDepthStream.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFPS.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFPS.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFirmware.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFirmware.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFixedParams.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorFixedParams.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorIRStream.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorIRStream.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorImageStream.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorImageStream.h create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.h create mode 100644 Source/Drivers/PS1080/Sensor/XnStreamProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnStreamProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.cpp create mode 100644 Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.h create mode 100644 Source/Drivers/PS1080/Sensor/YUV.cpp create mode 100644 Source/Drivers/PS1080/Sensor/YUV.h create mode 100644 Source/Drivers/PS1080/XnPsVersion.h create mode 100644 Source/Drivers/PSLink/Android.mk create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkExportedOniDriver.cpp create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.cpp create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.h create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniDevice.cpp create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniDevice.h create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniDriver.cpp create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniDriver.h create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.cpp create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.h create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.cpp create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.h create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniStream.cpp create mode 100644 Source/Drivers/PSLink/DriverImpl/LinkOniStream.h create mode 100644 Source/Drivers/PSLink/LinkDeviceEnumeration.cpp create mode 100644 Source/Drivers/PSLink/LinkDeviceEnumeration.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/IAsyncInputConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/IConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/IConnectionFactory.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/ILinkOutputStream.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/IOutputConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/ISyncIOConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/ISyncInputConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/LinkProtoLibVersion.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientSyncSocketConnection.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientSyncSocketConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnCyclicBuffer.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoLibDefs.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnMapSequenceListConverter.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxConnectionFactory.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxControlEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxOutDataEndpoint.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.h create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.cpp create mode 100644 Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.h create mode 100644 Source/Drivers/PSLink/Makefile create mode 100644 Source/Drivers/PSLink/PS1200Device.cpp create mode 100644 Source/Drivers/PSLink/PS1200Device.h create mode 100644 Source/Drivers/PSLink/PSLink.vcxproj create mode 100644 Source/Drivers/PSLink/PSLink.vcxproj.filters create mode 100644 Source/Drivers/PSLink/PSLinkConsole/Makefile create mode 100644 Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.cpp create mode 100644 Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj create mode 100644 Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj.filters create mode 100644 Source/Drivers/PSLink/PrimeClient.cpp create mode 100644 Source/Drivers/PSLink/PrimeClient.h create mode 100644 Source/Drivers/PSLink/PrimeClientDefs.h create mode 100644 Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkDefs.h create mode 100644 Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkProto.h create mode 100644 Source/Drivers/PSLink/XnPsVersion.h create mode 100644 Source/Drivers/TestDevice/TestDevice.cpp create mode 100644 Source/Drivers/TestDevice/TestDevice.vcxproj create mode 100644 Source/Resources/OpenNI.rc create mode 100644 Source/Resources/Resource-OpenNI.h create mode 100644 Source/Resources/mainicon.ico create mode 100644 Source/Tools/NiViewer/Audio.cpp create mode 100644 Source/Tools/NiViewer/Audio.h create mode 100644 Source/Tools/NiViewer/Capture.cpp create mode 100644 Source/Tools/NiViewer/Capture.h create mode 100644 Source/Tools/NiViewer/Device.cpp create mode 100644 Source/Tools/NiViewer/Device.h create mode 100644 Source/Tools/NiViewer/Draw.cpp create mode 100644 Source/Tools/NiViewer/Draw.h create mode 100644 Source/Tools/NiViewer/Keyboard.cpp create mode 100644 Source/Tools/NiViewer/Keyboard.h create mode 100644 Source/Tools/NiViewer/Makefile create mode 100644 Source/Tools/NiViewer/Menu.cpp create mode 100644 Source/Tools/NiViewer/Menu.h create mode 100644 Source/Tools/NiViewer/MouseInput.cpp create mode 100644 Source/Tools/NiViewer/MouseInput.h create mode 100644 Source/Tools/NiViewer/NiViewer.cpp create mode 100644 Source/Tools/NiViewer/NiViewer.vcxproj create mode 100644 ThirdParty/Android.mk create mode 100644 ThirdParty/GL/glh/glh_array.h create mode 100644 ThirdParty/GL/glh/glh_convenience.h create mode 100644 ThirdParty/GL/glh/glh_cube_map.h create mode 100644 ThirdParty/GL/glh/glh_extensions.h create mode 100644 ThirdParty/GL/glh/glh_genext.h create mode 100644 ThirdParty/GL/glh/glh_glut.h create mode 100644 ThirdParty/GL/glh/glh_glut2.h create mode 100644 ThirdParty/GL/glh/glh_glut_callfunc.h create mode 100644 ThirdParty/GL/glh/glh_glut_replay.h create mode 100644 ThirdParty/GL/glh/glh_glut_text.h create mode 100644 ThirdParty/GL/glh/glh_interactors.h create mode 100644 ThirdParty/GL/glh/glh_linear.h create mode 100644 ThirdParty/GL/glh/glh_mipmaps.h create mode 100644 ThirdParty/GL/glh/glh_obs.h create mode 100644 ThirdParty/GL/glh/glh_text.h create mode 100644 ThirdParty/GL/glut32.dll create mode 100644 ThirdParty/GL/glut32.lib create mode 100644 ThirdParty/GL/glut64.dll create mode 100644 ThirdParty/GL/glut64.lib create mode 100644 ThirdParty/LibJPEG/README create mode 100644 ThirdParty/LibJPEG/cderror.h create mode 100644 ThirdParty/LibJPEG/jcapimin.c create mode 100644 ThirdParty/LibJPEG/jcapistd.c create mode 100644 ThirdParty/LibJPEG/jccoefct.c create mode 100644 ThirdParty/LibJPEG/jccolor.c create mode 100644 ThirdParty/LibJPEG/jcdctmgr.c create mode 100644 ThirdParty/LibJPEG/jchuff.c create mode 100644 ThirdParty/LibJPEG/jchuff.h create mode 100644 ThirdParty/LibJPEG/jcinit.c create mode 100644 ThirdParty/LibJPEG/jcmainct.c create mode 100644 ThirdParty/LibJPEG/jcmarker.c create mode 100644 ThirdParty/LibJPEG/jcmaster.c create mode 100644 ThirdParty/LibJPEG/jcomapi.c create mode 100644 ThirdParty/LibJPEG/jconfig.h create mode 100644 ThirdParty/LibJPEG/jconfig.lnx86 create mode 100644 ThirdParty/LibJPEG/jconfig.ps3 create mode 100644 ThirdParty/LibJPEG/jconfig.vc create mode 100644 ThirdParty/LibJPEG/jcparam.c create mode 100644 ThirdParty/LibJPEG/jcphuff.c create mode 100644 ThirdParty/LibJPEG/jcprepct.c create mode 100644 ThirdParty/LibJPEG/jcsample.c create mode 100644 ThirdParty/LibJPEG/jctrans.c create mode 100644 ThirdParty/LibJPEG/jdapimin.c create mode 100644 ThirdParty/LibJPEG/jdapistd.c create mode 100644 ThirdParty/LibJPEG/jdatadst.c create mode 100644 ThirdParty/LibJPEG/jdatasrc.c create mode 100644 ThirdParty/LibJPEG/jdcoefct.c create mode 100644 ThirdParty/LibJPEG/jdcolor.c create mode 100644 ThirdParty/LibJPEG/jdct.h create mode 100644 ThirdParty/LibJPEG/jddctmgr.c create mode 100644 ThirdParty/LibJPEG/jdhuff.c create mode 100644 ThirdParty/LibJPEG/jdhuff.h create mode 100644 ThirdParty/LibJPEG/jdinput.c create mode 100644 ThirdParty/LibJPEG/jdmainct.c create mode 100644 ThirdParty/LibJPEG/jdmarker.c create mode 100644 ThirdParty/LibJPEG/jdmaster.c create mode 100644 ThirdParty/LibJPEG/jdmerge.c create mode 100644 ThirdParty/LibJPEG/jdphuff.c create mode 100644 ThirdParty/LibJPEG/jdpostct.c create mode 100644 ThirdParty/LibJPEG/jdsample.c create mode 100644 ThirdParty/LibJPEG/jdtrans.c create mode 100644 ThirdParty/LibJPEG/jerror.c create mode 100644 ThirdParty/LibJPEG/jerror.h create mode 100644 ThirdParty/LibJPEG/jfdctflt.c create mode 100644 ThirdParty/LibJPEG/jfdctfst.c create mode 100644 ThirdParty/LibJPEG/jfdctint.c create mode 100644 ThirdParty/LibJPEG/jidctflt.c create mode 100644 ThirdParty/LibJPEG/jidctfst.c create mode 100644 ThirdParty/LibJPEG/jidctint.c create mode 100644 ThirdParty/LibJPEG/jidctred.c create mode 100644 ThirdParty/LibJPEG/jinclude.h create mode 100644 ThirdParty/LibJPEG/jmemmgr.c create mode 100644 ThirdParty/LibJPEG/jmemnobs.c create mode 100644 ThirdParty/LibJPEG/jmemsys.h create mode 100644 ThirdParty/LibJPEG/jmorecfg.h create mode 100644 ThirdParty/LibJPEG/jpegint.h create mode 100644 ThirdParty/LibJPEG/jpeglib.h create mode 100644 ThirdParty/LibJPEG/jquant1.c create mode 100644 ThirdParty/LibJPEG/jquant2.c create mode 100644 ThirdParty/LibJPEG/jutils.c create mode 100644 ThirdParty/LibJPEG/jversion.h create mode 100644 ThirdParty/PSCommon/.gitignore create mode 100644 ThirdParty/PSCommon/Android.mk create mode 100644 ThirdParty/PSCommon/BuildSystem/BuildJavaWindows.py create mode 100644 ThirdParty/PSCommon/BuildSystem/CommonCSMakefile create mode 100644 ThirdParty/PSCommon/BuildSystem/CommonCppMakefile create mode 100644 ThirdParty/PSCommon/BuildSystem/CommonDefs.mak create mode 100644 ThirdParty/PSCommon/BuildSystem/CommonJavaMakefile create mode 100644 ThirdParty/PSCommon/BuildSystem/CommonTargets.mak create mode 100644 ThirdParty/PSCommon/BuildSystem/OpenNICommon.props create mode 100644 ThirdParty/PSCommon/BuildSystem/Platform.Arm create mode 100644 ThirdParty/PSCommon/BuildSystem/Platform.x64 create mode 100644 ThirdParty/PSCommon/BuildSystem/Platform.x86 create mode 100644 ThirdParty/PSCommon/RedistSystem/CopyToRepository.py create mode 100644 ThirdParty/PSCommon/RedistSystem/__init__.py create mode 100644 ThirdParty/PSCommon/RedistSystem/redist_base.py create mode 100644 ThirdParty/PSCommon/Testing/gmock-gtest-all.cc create mode 100644 ThirdParty/PSCommon/Testing/gmock/gmock.h create mode 100644 ThirdParty/PSCommon/Testing/gmock_main.cc create mode 100644 ThirdParty/PSCommon/Testing/gtest/gtest.h create mode 100644 ThirdParty/PSCommon/XnLib/Android.mk create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/amd64/WdfCoInstaller01009.dll create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/amd64/psdrv3.sys create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/dpinst-amd64.exe create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/dpinst-x86.exe create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/dpinst.xml create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/psdrv3.cat create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/psdrv3.ico create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/x86/WdfCoInstaller01009.dll create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Bin/x86/psdrv3.sys create mode 100644 ThirdParty/PSCommon/XnLib/Driver/Win32/Build/sys/PSDrvPublic.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/Android-Arm/XnPlatformAndroid-Arm.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/Linux-Arm/XnPlatformLinux-Arm.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/Linux-x86/XnOSLinux-x86.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/Linux-x86/XnPlatformLinux-x86.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/MacOSX/XnOSMacOSX.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/MacOSX/XnPlatformMacOSX.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/Win32/XnOSWin32.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/Win32/XnPlatformWin32.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/Win32/usb100.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnArray.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnBitSet.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnBox3D.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnCallStackLogger.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnCallback.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnCriticalSection.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnCyclicStack.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnDataStructures.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnDump.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnDumpWriters.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnErrorLogger.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnEvent.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnFPSCalculator.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnHash.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnLib.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnList.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnLockGuard.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnLockable.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnLog.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnLogTypes.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnLogWriterBase.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnMacros.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnMath.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnMatrix3x3.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnMemory.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnOS.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnOSCpp.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnOSStrings.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnPair.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnPlatform.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnPool.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnPriorityQueue.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnProfiling.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnProperty.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnQuaternion.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnQueue.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnSIMD-Neon.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnSIMD-None.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnSIMD-SSE.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnSIMD.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnScheduler.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnSmartPointer.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnStatus.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnStatusCodes.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnStatusRegister.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnString.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnStringsHash.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnSymmetricMatrix3x3.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnThreadSafeQueue.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnUSB.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnUSBDevice.h create mode 100644 ThirdParty/PSCommon/XnLib/Include/XnVector3D.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Android.mk create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxCriticalSections.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxDebug.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxEvents.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxEvents.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxFiles.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxINI.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxKeyboard.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxMemory.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxMutex.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxNetwork.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxPosixEvents.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxPosixEvents.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxPosixNamedEvents.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxPosixNamedEvents.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxProcesses.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxSharedLibs.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxSharedMemory.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxStrings.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxSysVNamedEvents.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxSysVNamedEvents.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxThreads.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxTime.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxUSB.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxUSB.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Linux/XnLinuxUSBDevice.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Makefile create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3CriticalSections.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Events.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Files.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3INI.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Memory.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Mutex.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Network.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3SharedLibs.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Strings.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Threads.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3Time.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3USB.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/PS3/XnPS3USB.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnUSBWin32.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnUSBWin32.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32CriticalSection.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Debug.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Events.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Files.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32INI.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Internal.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Keyboard.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Memory.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Mutex.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Network.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32OS.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Processes.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Semaphore.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32SharedLibrary.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32SharedMemory.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Strings.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Threads.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/XnWin32Time.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/devioctl.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/usb.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/usb100.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/Win32/usb200.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnDump.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnDumpFileWriter.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnDumpFileWriter.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnEnum.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnErrorLogger.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnFPSCalculator.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnFiles.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLib.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLib.vcxproj create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLib.vcxproj.filters create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLog.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLogAndroidWriter.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLogAndroidWriter.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLogConsoleWriter.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLogConsoleWriter.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLogFileWriter.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnLogFileWriter.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnOS.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnOSMemoryProfiling.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnProfiling.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnScheduler.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnStatus.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnStrings.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnSytmmetricMatrix3x3.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnThreads.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnUSB.cpp create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnUSBInternal.h create mode 100644 ThirdParty/PSCommon/XnLib/Source/XnVector3D.cpp create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/Android.mk create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_array.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_convenience.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_cube_map.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_extensions.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_genext.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_glut.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_glut2.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_glut_callfunc.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_glut_replay.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_glut_text.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_interactors.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_linear.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_mipmaps.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_obs.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glh/glh_text.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glut32.dll create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glut32.lib create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glut64.dll create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/GL/glut64.lib create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/AUTHORS create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/Android.mk create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/COPYING create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/ChangeLog create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/INSTALL create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/Makefile.am create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/Makefile.in create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/NEWS create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/PORTING create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/README create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/THANKS create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/TODO create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/aclocal.m4 create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/compile create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/config.guess create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/config.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/config.h.in create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/config.sub create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/configure create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/configure.ac create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/depcomp create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/doc/Makefile.am create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/doc/Makefile.in create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/doc/doxygen.cfg.in create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/examples/Makefile.am create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/examples/Makefile.in create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/examples/dpfp.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/examples/dpfp_threaded.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/examples/listdevs.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/install-sh create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb-1.0.pc.in create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/Makefile.am create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/Makefile.in create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/core.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/descriptor.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/io.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/libusb-1.0.def create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/libusb-1.0.rc create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/libusb.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/libusbi.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/darwin_usb.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/darwin_usb.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/linux_usbfs.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/linux_usbfs.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/openbsd_usb.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/poll_posix.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/poll_windows.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/poll_windows.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/threads_posix.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/threads_posix.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/threads_windows.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/threads_windows.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/windows_usb.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/os/windows_usb.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/sync.c create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/libusb/version.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/ltmain.sh create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/missing create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/config.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/ddk_build.cmd create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/inttypes.h create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb.dsw create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_dll.dsp create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_dll.vcproj create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_dll.vcxproj create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_dll.vcxproj.filters create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_sources create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_static.dsp create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_static.vcproj create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_static.vcxproj create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_static.vcxproj.filters create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_vs2005.sln create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/libusb_vs2010.sln create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/listdevs.dsp create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/listdevs.vcproj create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/listdevs.vcxproj create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/listdevs.vcxproj.filters create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/listdevs_sources create mode 100644 ThirdParty/PSCommon/XnLib/ThirdParty/libusb-1.0.9-Android/msvc/stdint.h create mode 100644 ThirdParty/PSCommon/XnLib/XnLib.sln create mode 100644 Wrappers/java/OpenNI.java/Build.bat create mode 100644 Wrappers/java/OpenNI.java/Makefile create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/CoordinateConverter.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/CropArea.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/Device.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/DeviceInfo.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/ImageRegistrationMode.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/NativeMethods.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/OpenNI.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/OutArg.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/PixelFormat.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/PlaybackControl.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/Point2D.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/Point3D.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/Recorder.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/SensorInfo.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/SensorType.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/Version.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/VideoFrameRef.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/VideoMode.java create mode 100644 Wrappers/java/OpenNI.java/src/org/openni/VideoStream.java create mode 100644 Wrappers/java/OpenNI.jni/CreateMethods.py create mode 100644 Wrappers/java/OpenNI.jni/Makefile create mode 100644 Wrappers/java/OpenNI.jni/OpenNI.jni.cpp create mode 100644 Wrappers/java/OpenNI.jni/OpenNI.jni.sln create mode 100644 Wrappers/java/OpenNI.jni/OpenNI.jni.vcxproj create mode 100644 Wrappers/java/OpenNI.jni/UpdateHeaders.bat create mode 100755 Wrappers/java/OpenNI.jni/UpdateHeaders.sh create mode 100644 Wrappers/java/OpenNI.jni/jni.h create mode 100644 Wrappers/java/OpenNI.jni/jni_md.h create mode 100644 Wrappers/java/OpenNI.jni/methods.inl create mode 100644 Wrappers/java/OpenNI.jni/org_openni_NativeMethods.cpp create mode 100644 Wrappers/java/OpenNI.jni/org_openni_NativeMethods.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..045f4ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +Bin +*.suo +*.user +*.opensdf +*.sdf +*.ipch +*.pyc +*.py~ +*.bz2 +source/Documentation/html +Redist/Output* +Redist/Install/obj* +Redist/build.* +Redist/Install/Fragments/RedistFragments.wxs +Redist/*-src +*.bak +Source/Documentation/xml +Temp +Redist/Final +Redist/compile.log +Redist/OpenNI-* +Source/Documentation/html diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..4e3e26b --- /dev/null +++ b/Android.mk @@ -0,0 +1,39 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Check if we're building from OS or NDK +ifdef TARGET_BUILD_VARIANT + OPENNI2_ANDROID_OS_BUILD := true +else + OPENNI2_ANDROID_NDK_BUILD := true +endif + +# Setup OpenNI2 local variables +OPENNI2_CFLAGS := -O3 -ftree-vectorize -ffast-math -funroll-loops -fPIC -fvisibility=hidden + +ifeq ($(ARCH_ARM_HAVE_ARMV7A),true) + OPENNI2_CFLAGS += -march=armv7-a -mfloat-abi=softfp -mtune=cortex-a9 -mfpu=vfp +endif + +ifeq ($(ARCH_ARM_HAVE_NEON),true) + OPENNI2_CFLAGS += -mfpu=neon -DHAVE_NEON=1 -flax-vector-conversions +endif + +# Recurse through all subdirs +include $(call all-subdir-makefiles) + +# Cleanup the local variables +OPENNI2_CFLAGS := diff --git a/Application.mk b/Application.mk new file mode 100644 index 0000000..9c7ff56 --- /dev/null +++ b/Application.mk @@ -0,0 +1,25 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# *** +# *** Note: This module file is only in use when building via NDK! *** +# *** + +# Android should be >= v2.3 +APP_PLATFORM := android-9 + +# Use ARM v7a instruction set +APP_ABI := armeabi-v7a +ARCH_ARM_HAVE_ARMV7A := true \ No newline at end of file diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..697e690 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,89 @@ +Change Log: +----------- + +OpenNI 2.2.0: + - Added getDepthColorSyncEnabled() API + - Added log settings API + - Add a typedef: OniGrayscale8Pixel + - invoke() method signature was updated - data is no longer const + - New Java Wrappers + - New Java sample: SimpleViewer.java + - Add support for Capri devkits + - Added tools: PS1080Console and PSLinkConsole (for debugging purposes) + - Drivers and INI files path resolution based on the shared library location (merge pull request #7. See https://github.com/OpenNI/OpenNI2/pull/7 for details. Thanks to Tomoto) + - NiViewer: Allow choosing which streams to open (run "NiViewer --help" for details). + - NiViewer: add support for seeking to a specific frame in a recording. Use the ':' key. + - Image registration support for Kinect driver (merge pull request #4. See https://github.com/OpenNI/OpenNI2/pull/4 for details. Thanks to Tomoto) + - Completely re-wrote frame buffers management. + - Added API for replacing frame buffer allocator. + - Linux: implement connect/disconnect events + - All samples are now self-contained and can be copied anywhere. In addition, compiling a sample does *not* overwrite the original precompiled binary. + - PS1080: IR stream now also supports RGB888 + - PS1080: improved hardware name detection + - PS1080: Add NESA unlimited to BIST + - PrimeSense: move common properties (PS1080 and Capri) to PrimeSense.h + - PS1080: add new property for turning on hardware frame sync, with no OpenNI checks + - New pixel format: YUYV. PS1080 now supports it (on newer firmwares), and so does NiViewer and recordings. + - PS1080: add lots of debug commands + - PS1080: add new file type (vendor data) + - PS1080: added support for the second alternative interface (low-bandwidth) + - PS1080: added support for IR stream from the AR130 CMOS + - PS1080: added support for turning on/off emitter + - Bug Fixes: + - Bug Fix: reading frames / waiting on streams did not work well from multiple threads + - Bug Fix: Failed to use after OpenCL was initialized + - Bug Fix: Visual Sutdio 2008 wasn't supported + - PS1080 Linux did not support the RD1.09 device + - PS1080 Bug Fix: Physical device name property could not be read + - Make sure device is still open when closing a stream that uses it. + - PS1080 Bug Fix: handle file system lock when uploading only in supporting firmwares + - PS1080 Bug Fix: LED API wasn't working + - PS1080 Bug Fix: debug dumps were not closed properly + - Kinect Bug Fix: wrong error code returned when trying to change video mode while streaming + - PS1080 Bug Fix: isPropertySupported() didn't return true for ONI_DEVICE_PROPERTY_FIRMWARE_VERSION, ONI_DEVICE_PROPERTY_HARDWARE_VERSION and ONI_DEVICE_PROPERTY_SERIAL_NUMBER. + - PS1080 Bug Fix: potential crash when corrupt data arrives on BAYER stream + - Build: + - Rename Redist dir to Packaging + - Remove all warnings during build. Treat all warnings as errors. + - Rewrote the android make files to proper standards + - Fix Linux identification (apparently -std=c1x defines only __linux and not linux) + - Support WIX 3.6 and up + - ReleaseVersion script won't fail if output file exists (overwrite it) + +OpenNI 2.1.0: + - API change: each event now has its own addListener()/removeListener() methods. A listener object can now be added only once. + - Support for Mac OSX + - Support for Linux on Arm + - Support for Android (native only) + - Kinect: implementing convertDepthToColorCoordinates() + - Kinect: implementing CameraSettings + - Kinect Bug Fix: can now switch between color and IR streams + - Kinect Bug Fix: wrong mirror value was returned + - PS1080 Bug Fix: trying to open more than 8 devices will crash + - PS1080 Bug Fix: on Linux 64-bit, color frames are sometimes corrupt + - PS1080 Bug Fix: a potential crash with older firmwares + - NiViewer now browses whenever a recording is started + - NiViewer: added 'i' key for toggling image-registration (also added current status in the status bar) + - Minor memory leak fixes + - EventBasedRead Sample Bug Fix: will not get device events + - EventBasedRead Sample now prints the list of connected devices and every change that occurs + - Recordings are now also compatible with OpenNI 1.x and NiTE 1.x + - Potential starvation bug fix when application takes much time handling events + - Log file now closes on shutdown(), and a new one is created on initialize() + - SimpleRead is now the default project in the VS solution (thanks eranws) + - Bug Fix: did not support Visual Studio 2008 and older + - Bug Fix: did not support Visual Studio 2012 and newer + - Bug Fix: did not support Visual Studio Express (thanks rh-galaxy) + - NiViewer Bug Fix: Mirror did not change IR state + - Kinect: providing Kinect for Windows PID and VID in the device info struct + - ONI files: providing driver name in the device info struct + - Bug Fix: ReleaseVersion script did not work on 32-bit machines + - Linux Bug Fix: log timestamps did not start from 0. + +OpenNI 2.0.0: + - Brand new API (see documentation) + - Algorithms API were removed, and are now part of middleware libraries (such as NiTE) + - New deployment model - private copy to each application (see documentation) + - Added support for turning off Auto Exposure and Auto White Balance of the color CMOS in PS1080 devices + - Built-in support for Kinect devices via the Kinect SDK (Windows only) + - Added support for translating a depth pixel to color map coordinates diff --git a/Config/OpenNI.ini b/Config/OpenNI.ini new file mode 100644 index 0000000..3375b84 --- /dev/null +++ b/Config/OpenNI.ini @@ -0,0 +1,14 @@ +[Log] +; 0 - Verbose; 1 - Info; 2 - Warning; 3 - Error. Default - None +Verbosity=3 +LogToConsole=0 +LogToFile=0 + +[Device] +;Override="" + +[Drivers] +; Location of the drivers specified by a relative path based on OpenNI's shared library or an absolute path. +; Path separator "/" can be used to be portable for any platforms. +; Default - OpenNI2/Drivers +;Repository=OpenNI2/Drivers diff --git a/Config/OpenNI2/Drivers/PS1080.ini b/Config/OpenNI2/Drivers/PS1080.ini new file mode 100644 index 0000000..c7c2348 --- /dev/null +++ b/Config/OpenNI2/Drivers/PS1080.ini @@ -0,0 +1,158 @@ +;---------------- Sensor Default Configuration ------------------- +[Device] +; Mirroring. 0 - Off (default), 1 - On +;Mirror=1 + +; FrameSync. 0 - Off (default), 1 - On +;FrameSync=1 + +; Stream Data Timestamps. 0 - milliseconds, 1 - microseconds (default) +;HighResTimestamps=1 + +; Stream Data Timestamps Source. 0 - Firmware (default), 1 - Host +;HostTimestamps=0 + +; A filter for the firmware log. Default is determined by firmware. +;FirmwareLogFilter=0 + +; Automatic firmare log retrieval. 0 - Off (default), or the number of milliseconds between log retrievals operations. +;FirmwareLogInterval=1000 + +; Print firmware log to console when automatic firmware log retrieval is on. 0 - Off (default), 1 - On +;FirmwareLogPrint=1 + +; Is APC enabled. 0 - Off, 1 - On (default) +;APCEnabled=1 + +; USB interface to be used. 0 - FW Default, 1 - ISO endpoints (default on Windows), 2 - BULK endpoints (default on Linux/Mac/Android machines), 3 - ISO endpoints for low-bandwidth depth +;UsbInterface=2 + +[Depth] +; Output format. 100 - 1mm depth values (default), 102 - u9.2 Shift values. +;OutputFormat=102 + +; Is stream mirrored. 0 - Off, 1 - On +;Mirror=1 + +; 0 - QVGA, 1 - VGA, 4 - QQVGA. Default: Arm - 4, other platforms - 0 +;Resolution=1 + +; Frames per second (default is 30) +;FPS=30 + +; Min depth cutoff. 0-10000 mm (default is 0) +;MinDepthValue=0 + +; Max depth cutoff. 0-10000 mm (default is 10000) +;MaxDepthValue=10000 + +; Input format. 0 - Uncompressed 16-bit, 1 - PS Compression, 3 - Packed 11-bit, 4 - Packed 12-bit. Default: Arm - 4, other platforms - 3 +;InputFormat=1 + +; Registration. 0 - Off (default), 1 - On +;Registration=1 + +; Registration Type. 0 - Don't care (default), 1 - use hardware accelaration, 2 - perform in software +;RegistrationType=0 + +; Hole Filler. 0 - Off, 1 - On (default) +;HoleFilter=1 + +; White Balance. 0 - Off, 1 - On (default) +;WhiteBalancedEnabled=1 + +; Gain. 0-50 (0 - Auto, 1 - Min., 50 - Max.). Default value is set by firmware. +;Gain=0 + +; Close Range Mode. 0 - Off (default), 1 - On +;CloseRange=0 + +; GMC Mode. 0 - Off, 1 - On (default) +;GMCMode=0 + +; GMC Debug. 0 - Off (default), 1 - On +;GMCDebug=1 + +; Depth Auto Gain Region-of-Interest. Default values are set by firmware. +;DepthAGCBin0MinDepth=500 +;DepthAGCBin0MaxDepth=800 +;DepthAGCBin1MinDepth=1500 +;DepthAGCBin1MaxDepth=1800 +;DepthAGCBin2MinDepth=2500 +;DepthAGCBin2MaxDepth=2800 +;DepthAGCBin3MinDepth=3500 +;DepthAGCBin3MaxDepth=3800 + +; Wavelength Correction Mechanism. 0 - Off (default), 1 - On +;WavelengthCorrection=1 + +; Wavelength Correction debug info. 0 - Off (default), 1 - On +;WavelengthCorrectionDebug=1 + +; Cropping mode. 1 - Normal (default), 2 - Increased FPS, 3 - Software only +;CroppingMode=1 + +; Cropping area +[Depth.Cropping] +;OffsetX=0 +;OffsetY=0 +;SizeX=320 +;SizeY=240 +;Enabled=1 + +[Image] +; Output format. 200 - RGB888 (default), 201 - YUV422, 202 - Gray8 (2.0 MP only), 205 - YUYV +;OutputFormat=200 + +; Is stream mirrored. 0 - Off, 1 - On +;Mirror=1 + +; 0 - QVGA (default), 1 - VGA, 2 - SXGA (1.3MP), 3 - UXGA (2.0MP), 14 - 720p, 15 - 1280x960 +;Resolution=1 + +; Frames per second (default is 30) +;FPS=30 + +; Input format. 0 - Compressed 8-bit BAYER (1.3MP or 2.0MP only), 1 - Compressed YUV422 (default in BULK), 2 - Jpeg, 5 - Uncompressed YUV422 (default in ISO), 6 - Uncompressed 8-bit BAYER (1.3MP or 2.0MP only), 7 - Uncompressed YUYV +;InputFormat=5 + +; Anti Flicker. 0 - Off (default), 50 - 50Hz, 60 - 60 Hz. +;Flicker=50 + +; Image quality when using Jpeg. 1-10 (1 - Lowest, 10 - Highest (default)) +;Quality=10 + +; Cropping mode. 1 - Normal (default), 2 - Increased FPS, 3 - Software only +;CroppingMode=1 + +; Cropping area +[Image.Cropping] +;OffsetX=0 +;OffsetY=0 +;SizeX=320 +;SizeY=240 +;Enabled=1 + +[IR] +; Output format. 200 - RGB888, 203 - Grayscale 16-bit (default) +;OutputFormat=203 + +; Is stream mirrored. 0 - Off, 1 - On +;Mirror=1 + +; 0 - QVGA (default), 1 - VGA, 2 - SXGA(1.3MP) +;Resolution=1 + +; Frames per second (default is 30) +;FPS=30 + +; Cropping mode. 1 - Normal (default), 2 - Increased FPS, 3 - Software only +;CroppingMode=1 + +; Cropping area +[IR.Cropping] +;OffsetX=0 +;OffsetY=0 +;SizeX=320 +;SizeY=240 +;Enabled=1 diff --git a/Config/OpenNI2/Drivers/PSLink.ini b/Config/OpenNI2/Drivers/PSLink.ini new file mode 100644 index 0000000..705a7bf --- /dev/null +++ b/Config/OpenNI2/Drivers/PSLink.ini @@ -0,0 +1,45 @@ +;---------------- PSLink Driver Default Configuration ------------------- + +[Device] +; USB interface to be used. 0 - FW Default, 1 - ISO endpoints (default on Windows), 2 - BULK endpoints (default on Linux/Mac/Android machines) +;UsbInterface=2 + +[Depth] +; Allows dumping all frames to files. 0 - Off (default), 1 - On +;DumpData=1 + +; Allow flipping the frame horizontally. 0 - Off, 1 - On (default) +;Mirror=0 + +; Compression of the data passed from device to host. 0 - None, 2 - 16z, 6 - 11-bit packed, 7 - 12-bit packed. Default is set by the firmware +;Compression=2 + +[Depth.VideoMode] +; Pixel Format. 100 - Depth 1 mm, 101 - Depth 100 um, 102 - Shifts 9.2, 103 - Shifts 9.3 +;PixelFormat=100 +; Requested X resolution +;XResolution=320 +; Requested Y resolution +;YResolution=240 +; Requested FPS +;FPS=30 + +[IR] +; Allows dumping all frames to files. 0 - Off (default), 1 - On +;DumpData=1 + +; Allow flipping the frame horizontally. 0 - Off, 1 - On (default) +;Mirror=0 + +; Compression of the data passed from device to host. 0 - None, 5 - 10-bit packed. Default is set by the firmware +;Compression=5 + +[IR.VideoMode] +; Pixel Format. 200 - RGB888, 202 - Grayscale 8-bit, 203 - Grayscale 16-bit +;PixelFormat=200 +; Requested X resolution +;XResolution=320 +; Requested Y resolution +;YResolution=240 +; Requested FPS +;FPS=30 diff --git a/Include/Android-Arm/OniPlatformAndroid-Arm.h b/Include/Android-Arm/OniPlatformAndroid-Arm.h new file mode 100644 index 0000000..1a3bca1 --- /dev/null +++ b/Include/Android-Arm/OniPlatformAndroid-Arm.h @@ -0,0 +1,43 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_PLATFORM_ANDROID_ARM_H_ +#define _ONI_PLATFORM_ANDROID_ARM_H_ + +// Start with Linux-x86, and override what's different +#include "../Linux-x86/OniPlatformLinux-x86.h" + +//--------------------------------------------------------------------------- +// Platform Basic Definition +//--------------------------------------------------------------------------- +#undef ONI_PLATFORM +#undef ONI_PLATFORM_STRING + +#define ONI_PLATFORM ONI_PLATFORM_ANDROID_ARM +#define ONI_PLATFORM_STRING "Android-Arm" + +#ifdef HAVE_ANDROID_OS + #define ONI_PLATFORM_ANDROID_OS + + #undef ONI_PLATFORM_STRING + #define ONI_PLATFORM_STRING "AndroidOS-Arm" +#endif + +#endif //_ONI_PLATFORM_LINUX_ARM_H_ diff --git a/Include/Driver/OniDriverAPI.h b/Include/Driver/OniDriverAPI.h new file mode 100644 index 0000000..c41e1f6 --- /dev/null +++ b/Include/Driver/OniDriverAPI.h @@ -0,0 +1,378 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_DRIVER_API_H_ +#define _ONI_DRIVER_API_H_ + +#include "OniPlatform.h" +#include "OniCTypes.h" +#include "OniCProperties.h" +#include "OniDriverTypes.h" +#include + +namespace oni { namespace driver { + +class DeviceBase; +class StreamBase; + +typedef void (ONI_CALLBACK_TYPE* DeviceConnectedCallback)(const OniDeviceInfo*, void* pCookie); +typedef void (ONI_CALLBACK_TYPE* DeviceDisconnectedCallback)(const OniDeviceInfo*, void* pCookie); +typedef void (ONI_CALLBACK_TYPE* DeviceStateChangedCallback)(const OniDeviceInfo* deviceId, int errorState, void* pCookie); +typedef void (ONI_CALLBACK_TYPE* NewFrameCallback)(StreamBase* streamId, OniFrame*, void* pCookie); +typedef void (ONI_CALLBACK_TYPE* PropertyChangedCallback)(void* sender, int propertyId, const void* data, int dataSize, void* pCookie); + +class StreamServices : public OniStreamServices +{ +public: + int getDefaultRequiredFrameSize() + { + return OniStreamServices::getDefaultRequiredFrameSize(streamServices); + } + + OniFrame* acquireFrame() + { + return OniStreamServices::acquireFrame(streamServices); + } + + void addFrameRef(OniFrame* pFrame) + { + OniStreamServices::addFrameRef(streamServices, pFrame); + } + + void releaseFrame(OniFrame* pFrame) + { + OniStreamServices::releaseFrame(streamServices, pFrame); + } +}; + +class StreamBase +{ +public: + StreamBase() : m_newFrameCallback(NULL), m_propertyChangedCallback(NULL) {} + virtual ~StreamBase() {} + + virtual void setServices(StreamServices* pStreamServices) { m_pServices = pStreamServices; } + + virtual OniStatus setProperty(int /*propertyId*/, const void* /*data*/, int /*dataSize*/) {return ONI_STATUS_NOT_IMPLEMENTED;} + virtual OniStatus getProperty(int /*propertyId*/, void* /*data*/, int* /*pDataSize*/) {return ONI_STATUS_NOT_IMPLEMENTED;} + virtual OniBool isPropertySupported(int /*propertyId*/) {return FALSE;} + virtual OniStatus invoke(int /*commandId*/, void* /*data*/, int /*dataSize*/) {return ONI_STATUS_NOT_IMPLEMENTED;} + virtual OniBool isCommandSupported(int /*commandId*/) {return FALSE;} + + virtual int getRequiredFrameSize() { return getServices().getDefaultRequiredFrameSize(); } + + virtual OniStatus start() = 0; + virtual void stop() = 0; + + virtual void setNewFrameCallback(NewFrameCallback handler, void* pCookie) { m_newFrameCallback = handler; m_newFrameCallbackCookie = pCookie; } + virtual void setPropertyChangedCallback(PropertyChangedCallback handler, void* pCookie) { m_propertyChangedCallback = handler; m_propertyChangedCookie = pCookie; } + + virtual void notifyAllProperties() { return; } + + virtual OniStatus convertDepthToColorCoordinates(StreamBase* /*colorStream*/, int /*depthX*/, int /*depthY*/, OniDepthPixel /*depthZ*/, int* /*pColorX*/, int* /*pColorY*/) { return ONI_STATUS_NOT_SUPPORTED; } + +protected: + void raiseNewFrame(OniFrame* pFrame) { (*m_newFrameCallback)(this, pFrame, m_newFrameCallbackCookie); } + void raisePropertyChanged(int propertyId, const void* data, int dataSize) { (*m_propertyChangedCallback)(this, propertyId, data, dataSize, m_propertyChangedCookie); } + + StreamServices& getServices() { return *m_pServices; } + +private: + StreamServices* m_pServices; + NewFrameCallback m_newFrameCallback; + void* m_newFrameCallbackCookie; + PropertyChangedCallback m_propertyChangedCallback; + void* m_propertyChangedCookie; +}; + +class DeviceBase +{ +public: + DeviceBase() {} + virtual ~DeviceBase() {} + + virtual OniStatus getSensorInfoList(OniSensorInfo** pSensorInfos, int* numSensors) = 0; + + virtual StreamBase* createStream(OniSensorType) = 0; + virtual void destroyStream(StreamBase* pStream) = 0; + + virtual OniStatus setProperty(int /*propertyId*/, const void* /*data*/, int /*dataSize*/) {return ONI_STATUS_NOT_IMPLEMENTED;} + virtual OniStatus getProperty(int /*propertyId*/, void* /*data*/, int* /*pDataSize*/) {return ONI_STATUS_NOT_IMPLEMENTED;} + virtual OniBool isPropertySupported(int /*propertyId*/) {return FALSE;} + virtual OniStatus invoke(int /*commandId*/, void* /*data*/, int /*dataSize*/) {return ONI_STATUS_NOT_IMPLEMENTED;} + virtual OniBool isCommandSupported(int /*commandId*/) {return FALSE;} + virtual OniStatus tryManualTrigger() {return ONI_STATUS_OK;} + + virtual void setPropertyChangedCallback(PropertyChangedCallback handler, void* pCookie) { m_propertyChangedCallback = handler; m_propertyChangedCookie = pCookie; } + virtual void notifyAllProperties() { return; } + + virtual OniBool isImageRegistrationModeSupported(OniImageRegistrationMode mode) { return (mode == ONI_IMAGE_REGISTRATION_OFF); } + +protected: + void raisePropertyChanged(int propertyId, const void* data, int dataSize) { (*m_propertyChangedCallback)(this, propertyId, data, dataSize, m_propertyChangedCookie); } + +private: + PropertyChangedCallback m_propertyChangedCallback; + void* m_propertyChangedCookie; +}; + +class DriverServices +{ +public: + DriverServices(OniDriverServices* pDriverServices) : m_pDriverServices(pDriverServices) {} + + void errorLoggerAppend(const char* format, ...) + { + va_list args; + va_start(args, format); + m_pDriverServices->errorLoggerAppend(m_pDriverServices->driverServices, format, args); + va_end(args); + } + + void errorLoggerClear() + { + m_pDriverServices->errorLoggerClear(m_pDriverServices->driverServices); + } + + void log(int severity, const char* file, int line, const char* mask, const char* message) + { + m_pDriverServices->log(m_pDriverServices->driverServices, severity, file, line, mask, message); + } + +private: + OniDriverServices* m_pDriverServices; +}; + +class DriverBase +{ +public: + DriverBase(OniDriverServices* pDriverServices) : m_services(pDriverServices) + {} + + virtual ~DriverBase() {} + + virtual OniStatus initialize(DeviceConnectedCallback connectedCallback, DeviceDisconnectedCallback disconnectedCallback, DeviceStateChangedCallback deviceStateChangedCallback, void* pCookie) + { + m_deviceConnectedEvent = connectedCallback; + m_deviceDisconnectedEvent = disconnectedCallback; + m_deviceStateChangedEvent = deviceStateChangedCallback; + m_pCookie = pCookie; + return ONI_STATUS_OK; + } + + virtual DeviceBase* deviceOpen(const char* uri, const char* mode) = 0; + virtual void deviceClose(DeviceBase* pDevice) = 0; + + virtual void shutdown() = 0; + + virtual OniStatus tryDevice(const char* /*uri*/) { return ONI_STATUS_ERROR;} + + virtual void* enableFrameSync(StreamBase** /*pStreams*/, int /*streamCount*/) { return NULL; } + virtual void disableFrameSync(void* /*frameSyncGroup*/) {} + +protected: + void deviceConnected(const OniDeviceInfo* pInfo) { (m_deviceConnectedEvent)(pInfo, m_pCookie); } + void deviceDisconnected(const OniDeviceInfo* pInfo) { (m_deviceDisconnectedEvent)(pInfo, m_pCookie); } + void deviceStateChanged(const OniDeviceInfo* pInfo, int errorState) { (m_deviceStateChangedEvent)(pInfo, errorState, m_pCookie); } + + DriverServices& getServices() { return m_services; } + +private: + DeviceConnectedCallback m_deviceConnectedEvent; + DeviceDisconnectedCallback m_deviceDisconnectedEvent; + DeviceStateChangedCallback m_deviceStateChangedEvent; + void* m_pCookie; + + DriverServices m_services; +}; + +}} // oni::driver + +#define ONI_EXPORT_DRIVER(DriverClass) \ + \ +oni::driver::DriverBase* g_pDriver = NULL; \ + \ +/* As Driver */ \ +ONI_C_API_EXPORT void oniDriverCreate(OniDriverServices* driverServices) { \ + g_pDriver = XN_NEW(DriverClass, driverServices); \ +} \ +ONI_C_API_EXPORT void oniDriverDestroy() \ +{ \ + g_pDriver->shutdown(); \ + XN_DELETE(g_pDriver); g_pDriver = NULL; \ +} \ +ONI_C_API_EXPORT OniStatus oniDriverInitialize(oni::driver::DeviceConnectedCallback deviceConnectedCallback, \ + oni::driver::DeviceDisconnectedCallback deviceDisconnectedCallback, \ + oni::driver::DeviceStateChangedCallback deviceStateChangedCallback, \ + void* pCookie) \ +{ \ + return g_pDriver->initialize(deviceConnectedCallback, deviceDisconnectedCallback, deviceStateChangedCallback, pCookie); \ +} \ + \ +ONI_C_API_EXPORT OniStatus oniDriverTryDevice(const char* uri) \ +{ \ + return g_pDriver->tryDevice(uri); \ +} \ + \ +/* As Device */ \ +ONI_C_API_EXPORT oni::driver::DeviceBase* oniDriverDeviceOpen(const char* uri, const char* mode) \ +{ \ + return g_pDriver->deviceOpen(uri, mode); \ +} \ +ONI_C_API_EXPORT void oniDriverDeviceClose(oni::driver::DeviceBase* pDevice) \ +{ \ + g_pDriver->deviceClose(pDevice); \ +} \ + \ +ONI_C_API_EXPORT OniStatus oniDriverDeviceGetSensorInfoList(oni::driver::DeviceBase* pDevice, OniSensorInfo** pSensorInfos, \ + int* numSensors) \ +{ \ + return pDevice->getSensorInfoList(pSensorInfos, numSensors); \ +} \ + \ +ONI_C_API_EXPORT oni::driver::StreamBase* oniDriverDeviceCreateStream(oni::driver::DeviceBase* pDevice, \ + OniSensorType sensorType) \ +{ \ + return pDevice->createStream(sensorType); \ +} \ + \ +ONI_C_API_EXPORT void oniDriverDeviceDestroyStream(oni::driver::DeviceBase* pDevice, oni::driver::StreamBase* pStream) \ +{ \ + return pDevice->destroyStream(pStream); \ +} \ + \ +ONI_C_API_EXPORT OniStatus oniDriverDeviceSetProperty(oni::driver::DeviceBase* pDevice, int propertyId, \ + const void* data, int dataSize) \ +{ \ + return pDevice->setProperty(propertyId, data, dataSize); \ +} \ +ONI_C_API_EXPORT OniStatus oniDriverDeviceGetProperty(oni::driver::DeviceBase* pDevice, int propertyId, \ + void* data, int* pDataSize) \ +{ \ + return pDevice->getProperty(propertyId, data, pDataSize); \ +} \ +ONI_C_API_EXPORT OniBool oniDriverDeviceIsPropertySupported(oni::driver::DeviceBase* pDevice, int propertyId) \ +{ \ + return pDevice->isPropertySupported(propertyId); \ +} \ +ONI_C_API_EXPORT void oniDriverDeviceSetPropertyChangedCallback(oni::driver::DeviceBase* pDevice, \ + oni::driver::PropertyChangedCallback handler, void* pCookie) \ +{ \ + pDevice->setPropertyChangedCallback(handler, pCookie); \ +} \ +ONI_C_API_EXPORT void oniDriverDeviceNotifyAllProperties(oni::driver::DeviceBase* pDevice) \ +{ \ + pDevice->notifyAllProperties(); \ +} \ +ONI_C_API_EXPORT OniStatus oniDriverDeviceInvoke(oni::driver::DeviceBase* pDevice, int commandId, \ + void* data, int dataSize) \ +{ \ + return pDevice->invoke(commandId, data, dataSize); \ +} \ +ONI_C_API_EXPORT OniBool oniDriverDeviceIsCommandSupported(oni::driver::DeviceBase* pDevice, int commandId) \ +{ \ + return pDevice->isCommandSupported(commandId); \ +} \ +ONI_C_API_EXPORT OniStatus oniDriverDeviceTryManualTrigger(oni::driver::DeviceBase* pDevice) \ +{ \ + return pDevice->tryManualTrigger(); \ +} \ +ONI_C_API_EXPORT OniBool oniDriverDeviceIsImageRegistrationModeSupported(oni::driver::DeviceBase* pDevice, \ + OniImageRegistrationMode mode) \ +{ \ + return pDevice->isImageRegistrationModeSupported(mode); \ +} \ + \ +/* As Stream */ \ +ONI_C_API_EXPORT void oniDriverStreamSetServices(oni::driver::StreamBase* pStream, OniStreamServices* pServices) \ +{ \ + pStream->setServices((oni::driver::StreamServices*)pServices); \ +} \ + \ +ONI_C_API_EXPORT OniStatus oniDriverStreamSetProperty(oni::driver::StreamBase* pStream, int propertyId, \ + const void* data, int dataSize) \ +{ \ + return pStream->setProperty(propertyId, data, dataSize); \ +} \ +ONI_C_API_EXPORT OniStatus oniDriverStreamGetProperty(oni::driver::StreamBase* pStream, int propertyId, void* data, \ + int* pDataSize) \ +{ \ + return pStream->getProperty(propertyId, data, pDataSize); \ +} \ +ONI_C_API_EXPORT OniBool oniDriverStreamIsPropertySupported(oni::driver::StreamBase* pStream, int propertyId) \ +{ \ + return pStream->isPropertySupported(propertyId); \ +} \ +ONI_C_API_EXPORT void oniDriverStreamSetPropertyChangedCallback(oni::driver::StreamBase* pStream, \ + oni::driver::PropertyChangedCallback handler, void* pCookie) \ +{ \ + pStream->setPropertyChangedCallback(handler, pCookie); \ +} \ +ONI_C_API_EXPORT void oniDriverStreamNotifyAllProperties(oni::driver::StreamBase* pStream) \ +{ \ + pStream->notifyAllProperties(); \ +} \ +ONI_C_API_EXPORT OniStatus oniDriverStreamInvoke(oni::driver::StreamBase* pStream, int commandId, \ + void* data, int dataSize) \ +{ \ + return pStream->invoke(commandId, data, dataSize); \ +} \ +ONI_C_API_EXPORT OniBool oniDriverStreamIsCommandSupported(oni::driver::StreamBase* pStream, int commandId) \ +{ \ + return pStream->isCommandSupported(commandId); \ +} \ + \ +ONI_C_API_EXPORT OniStatus oniDriverStreamStart(oni::driver::StreamBase* pStream) \ +{ \ + return pStream->start(); \ +} \ +ONI_C_API_EXPORT void oniDriverStreamStop(oni::driver::StreamBase* pStream) \ +{ \ + pStream->stop(); \ +} \ + \ +ONI_C_API_EXPORT int oniDriverStreamGetRequiredFrameSize(oni::driver::StreamBase* pStream) \ +{ \ + return pStream->getRequiredFrameSize(); \ +} \ + \ +ONI_C_API_EXPORT void oniDriverStreamSetNewFrameCallback(oni::driver::StreamBase* pStream, \ + oni::driver::NewFrameCallback handler, void* pCookie) \ +{ \ + pStream->setNewFrameCallback(handler, pCookie); \ +} \ + \ +ONI_C_API_EXPORT OniStatus oniDriverStreamConvertDepthToColorCoordinates(oni::driver::StreamBase* pDepthStream, \ + oni::driver::StreamBase* pColorStream, int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY) \ +{ \ + return pDepthStream->convertDepthToColorCoordinates(pColorStream, depthX, depthY, depthZ, pColorX, pColorY); \ +} \ + \ +ONI_C_API_EXPORT void* oniDriverEnableFrameSync(oni::driver::StreamBase** pStreams, int streamCount) \ +{ \ + return g_pDriver->enableFrameSync(pStreams, streamCount); \ +} \ + \ +ONI_C_API_EXPORT void oniDriverDisableFrameSync(void* frameSyncGroup) \ +{ \ + return g_pDriver->disableFrameSync(frameSyncGroup); \ +} \ + +#endif // _ONI_DRIVER_API_H_ diff --git a/Include/Driver/OniDriverTypes.h b/Include/Driver/OniDriverTypes.h new file mode 100644 index 0000000..fe8cd44 --- /dev/null +++ b/Include/Driver/OniDriverTypes.h @@ -0,0 +1,54 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_DRIVER_TYPES_H_ +#define _ONI_DRIVER_TYPES_H_ + +#include +#include + +#define ONI_STREAM_PROPERTY_PRIVATE_BASE XN_MAX_UINT16 + +typedef struct +{ + int dataSize; + void* data; +} OniGeneralBuffer; + +/////// DriverServices +struct OniDriverServices +{ + void* driverServices; + void (ONI_CALLBACK_TYPE* errorLoggerAppend)(void* driverServices, const char* format, va_list args); + void (ONI_CALLBACK_TYPE* errorLoggerClear)(void* driverServices); + void (ONI_CALLBACK_TYPE* log)(void* driverServices, int severity, const char* file, int line, const char* mask, const char* message); +}; + +struct OniStreamServices +{ + void* streamServices; + int (ONI_CALLBACK_TYPE* getDefaultRequiredFrameSize)(void* streamServices); + OniFrame* (ONI_CALLBACK_TYPE* acquireFrame)(void* streamServices); // returns a frame with size corresponding to getRequiredFrameSize() + void (ONI_CALLBACK_TYPE* addFrameRef)(void* streamServices, OniFrame* pframe); + void (ONI_CALLBACK_TYPE* releaseFrame)(void* streamServices, OniFrame* pframe); +}; + + +#endif // _ONI_DRIVER_TYPES_H_ diff --git a/Include/Linux-Arm/OniPlatformLinux-Arm.h b/Include/Linux-Arm/OniPlatformLinux-Arm.h new file mode 100644 index 0000000..fb96323 --- /dev/null +++ b/Include/Linux-Arm/OniPlatformLinux-Arm.h @@ -0,0 +1,36 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_PLATFORM_LINUX_ARM_H_ +#define _ONI_PLATFORM_LINUX_ARM_H_ + +// Start with Linux-x86, and override what's different +#include "../Linux-x86/OniPlatformLinux-x86.h" + +//--------------------------------------------------------------------------- +// Platform Basic Definition +//--------------------------------------------------------------------------- +#undef ONI_PLATFORM +#undef ONI_PLATFORM_STRING +#define ONI_PLATFORM ONI_PLATFORM_LINUX_ARM +#define ONI_PLATFORM_STRING "Linux-Arm" + +#endif //_ONI_PLATFORM_LINUX_ARM_H_ + diff --git a/Include/Linux-x86/OniPlatformLinux-x86.h b/Include/Linux-x86/OniPlatformLinux-x86.h new file mode 100644 index 0000000..e5980f3 --- /dev/null +++ b/Include/Linux-x86/OniPlatformLinux-x86.h @@ -0,0 +1,102 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_PLATFORM_LINUX_X86_H_ +#define _ONI_PLATFORM_LINUX_X86_H_ + +//--------------------------------------------------------------------------- +// Prerequisites +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Platform Basic Definition +//--------------------------------------------------------------------------- +#define ONI_PLATFORM ONI_PLATFORM_LINUX_X86 +#define ONI_PLATFORM_STRING "Linux-x86" + +//--------------------------------------------------------------------------- +// Platform Capabilities +//--------------------------------------------------------------------------- +#define ONI_PLATFORM_ENDIAN_TYPE ONI_PLATFORM_IS_LITTLE_ENDIAN + +#define ONI_PLATFORM_SUPPORTS_DYNAMIC_LIBS 1 + +//--------------------------------------------------------------------------- +// Memory +//--------------------------------------------------------------------------- +/** The default memory alignment. */ +#define ONI_DEFAULT_MEM_ALIGN 16 + +/** The thread static declarator (using TLS). */ +#define ONI_THREAD_STATIC __thread + +//--------------------------------------------------------------------------- +// Files +//--------------------------------------------------------------------------- +/** The maximum allowed file path size (in bytes). */ +#define ONI_FILE_MAX_PATH 256 + +//--------------------------------------------------------------------------- +// Call back +//--------------------------------------------------------------------------- +/** The std call type. */ +#define ONI_STDCALL __stdcall + +/** The call back calling convention. */ +#define ONI_CALLBACK_TYPE + +/** The C and C++ calling convension. */ +#define ONI_C_DECL + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +/** Returns the date and time at compile time. */ +#define ONI_TIMESTAMP __DATE__ " " __TIME__ + +/** Converts n into a pre-processor string. */ +#define ONI_STRINGIFY(n) ONI_STRINGIFY_HELPER(n) +#define ONI_STRINGIFY_HELPER(n) #n + +//--------------------------------------------------------------------------- +// API Export/Import Macros +//--------------------------------------------------------------------------- +/** Indicates an exported shared library function. */ +#define ONI_API_EXPORT __attribute__ ((visibility("default"))) + +/** Indicates an imported shared library function. */ +#define ONI_API_IMPORT + +/** Indicates a deprecated function */ +#define ONI_API_DEPRECATED(msg) __attribute__((warning("This function is deprecated: " msg))) + +#endif //_ONI_PLATFORM_LINUX_X86_H_ + diff --git a/Include/MacOSX/OniPlatformMacOSX.h b/Include/MacOSX/OniPlatformMacOSX.h new file mode 100644 index 0000000..251256e --- /dev/null +++ b/Include/MacOSX/OniPlatformMacOSX.h @@ -0,0 +1,42 @@ +/***************************************************************************** +* * +* PrimeSense PSCommon Library * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of PSCommon. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_PLATFORM_MACOSX_H_ +#define _ONI_PLATFORM_MACOSX_H_ + +// Start with Linux-x86, and override what's different +#include "../Linux-x86/OniPlatformLinux-x86.h" + +#include + +#undef ONI_PLATFORM +#undef ONI_PLATFORM_STRING +#define ONI_PLATFORM ONI_PLATFORM_MACOSX +#define ONI_PLATFORM_STRING "MacOSX" + +#define ONI_PLATFORM_HAS_NO_TIMED_OPS +#define ONI_PLATFORM_HAS_NO_CLOCK_GETTIME +#define ONI_PLATFORM_HAS_NO_SCHED_PARAM +#define ONI_PLATFORM_HAS_BUILTIN_SEMUN + +#undef ONI_THREAD_STATIC +#define ONI_THREAD_STATIC + +#endif //_ONI_PLATFORM_MACOSX_H_ diff --git a/Include/OniCAPI.h b/Include/OniCAPI.h new file mode 100644 index 0000000..aea426d --- /dev/null +++ b/Include/OniCAPI.h @@ -0,0 +1,259 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_C_API_H_ +#define _ONI_C_API_H_ + +#include "OniPlatform.h" +#include "OniCTypes.h" +#include "OniCProperties.h" +#include "OniVersion.h" + +/******************************************** General APIs */ + +/** Initialize OpenNI2. Use ONI_API_VERSION as the version. */ +ONI_C_API OniStatus oniInitialize(int apiVersion); +/** Shutdown OpenNI2 */ +ONI_C_API void oniShutdown(); + +/** + * Get the list of currently connected device. + * Each device is represented by its OniDeviceInfo. + * pDevices will be allocated inside. + */ +ONI_C_API OniStatus oniGetDeviceList(OniDeviceInfo** pDevices, int* pNumDevices); +/** Release previously allocated device list */ +ONI_C_API OniStatus oniReleaseDeviceList(OniDeviceInfo* pDevices); + +ONI_C_API OniStatus oniRegisterDeviceCallbacks(OniDeviceCallbacks* pCallbacks, void* pCookie, OniCallbackHandle* pHandle); +ONI_C_API void oniUnregisterDeviceCallbacks(OniCallbackHandle handle); + +/** Wait for any of the streams to have a new frame */ +ONI_C_API OniStatus oniWaitForAnyStream(OniStreamHandle* pStreams, int numStreams, int* pStreamIndex, int timeout); + +/** Get the current version of OpenNI2 */ +ONI_C_API OniVersion oniGetVersion(); + +/** Translate from format to number of bytes per pixel. Will return 0 for formats in which the number of bytes per pixel isn't fixed. */ +ONI_C_API int oniFormatBytesPerPixel(OniPixelFormat format); + +/** Get internal error */ +ONI_C_API const char* oniGetExtendedError(); + +/******************************************** Device APIs */ + +/** Open a device. Uri can be taken from the matching OniDeviceInfo. */ +ONI_C_API OniStatus oniDeviceOpen(const char* uri, OniDeviceHandle* pDevice); +/** Close a device */ +ONI_C_API OniStatus oniDeviceClose(OniDeviceHandle device); + +/** Get the possible configurations available for a specific source, or NULL if the source does not exist. */ +ONI_C_API const OniSensorInfo* oniDeviceGetSensorInfo(OniDeviceHandle device, OniSensorType sensorType); + +/** Get the OniDeviceInfo of a certain device. */ +ONI_C_API OniStatus oniDeviceGetInfo(OniDeviceHandle device, OniDeviceInfo* pInfo); + +/** Create a new stream in the device. The stream will originate from the source. */ +ONI_C_API OniStatus oniDeviceCreateStream(OniDeviceHandle device, OniSensorType sensorType, OniStreamHandle* pStream); + +ONI_C_API OniStatus oniDeviceEnableDepthColorSync(OniDeviceHandle device); +ONI_C_API void oniDeviceDisableDepthColorSync(OniDeviceHandle device); +ONI_C_API OniBool oniDeviceGetDepthColorSyncEnabled(OniDeviceHandle device); + +/** Set property in the device. Use the properties listed in OniTypes.h: ONI_DEVICE_PROPERTY_..., or specific ones supplied by the device. */ +ONI_C_API OniStatus oniDeviceSetProperty(OniDeviceHandle device, int propertyId, const void* data, int dataSize); +/** Get property in the device. Use the properties listed in OniTypes.h: ONI_DEVICE_PROPERTY_..., or specific ones supplied by the device. */ +ONI_C_API OniStatus oniDeviceGetProperty(OniDeviceHandle device, int propertyId, void* data, int* pDataSize); +/** Check if the property is supported by the device. Use the properties listed in OniTypes.h: ONI_DEVICE_PROPERTY_..., or specific ones supplied by the device. */ +ONI_C_API OniBool oniDeviceIsPropertySupported(OniDeviceHandle device, int propertyId); +/** Invoke an internal functionality of the device. */ +ONI_C_API OniStatus oniDeviceInvoke(OniDeviceHandle device, int commandId, void* data, int dataSize); +/** Check if a command is supported, for invoke */ +ONI_C_API OniBool oniDeviceIsCommandSupported(OniDeviceHandle device, int commandId); + +ONI_C_API OniBool oniDeviceIsImageRegistrationModeSupported(OniDeviceHandle device, OniImageRegistrationMode mode); + +/** @internal */ +ONI_C_API OniStatus oniDeviceOpenEx(const char* uri, const char* mode, OniDeviceHandle* pDevice); + +/******************************************** Stream APIs */ + +/** Destroy an existing stream */ +ONI_C_API void oniStreamDestroy(OniStreamHandle stream); + +/** Get the OniSensorInfo of the certain stream. */ +ONI_C_API const OniSensorInfo* oniStreamGetSensorInfo(OniStreamHandle stream); + +/** Start generating data from the stream. */ +ONI_C_API OniStatus oniStreamStart(OniStreamHandle stream); +/** Stop generating data from the stream. */ +ONI_C_API void oniStreamStop(OniStreamHandle stream); + +/** Get the next frame from the stream. This function is blocking until there is a new frame from the stream. For timeout, use oniWaitForStreams() first */ +ONI_C_API OniStatus oniStreamReadFrame(OniStreamHandle stream, OniFrame** pFrame); + +/** Register a callback to when the stream has a new frame. */ +ONI_C_API OniStatus oniStreamRegisterNewFrameCallback(OniStreamHandle stream, OniNewFrameCallback handler, void* pCookie, OniCallbackHandle* pHandle); +/** Unregister a previously registered callback to when the stream has a new frame. */ +ONI_C_API void oniStreamUnregisterNewFrameCallback(OniStreamHandle stream, OniCallbackHandle handle); + +/** Set property in the stream. Use the properties listed in OniTypes.h: ONI_STREAM_PROPERTY_..., or specific ones supplied by the device for its streams. */ +ONI_C_API OniStatus oniStreamSetProperty(OniStreamHandle stream, int propertyId, const void* data, int dataSize); +/** Get property in the stream. Use the properties listed in OniTypes.h: ONI_STREAM_PROPERTY_..., or specific ones supplied by the device for its streams. */ +ONI_C_API OniStatus oniStreamGetProperty(OniStreamHandle stream, int propertyId, void* data, int* pDataSize); +/** Check if the property is supported the stream. Use the properties listed in OniTypes.h: ONI_STREAM_PROPERTY_..., or specific ones supplied by the device for its streams. */ +ONI_C_API OniBool oniStreamIsPropertySupported(OniStreamHandle stream, int propertyId); +/** Invoke an internal functionality of the stream. */ +ONI_C_API OniStatus oniStreamInvoke(OniStreamHandle stream, int commandId, void* data, int dataSize); +/** Check if a command is supported, for invoke */ +ONI_C_API OniBool oniStreamIsCommandSupported(OniStreamHandle stream, int commandId); +/** Sets the stream buffer allocation functions. Note that this function may only be called while stream is not started. */ +ONI_C_API OniStatus oniStreamSetFrameBuffersAllocator(OniStreamHandle stream, OniFrameAllocBufferCallback alloc, OniFrameFreeBufferCallback free, void* pCookie); + +//// +/** Mark another user of the frame. */ +ONI_C_API void oniFrameAddRef(OniFrame* pFrame); +/** Mark that the frame is no longer needed. */ +ONI_C_API void oniFrameRelease(OniFrame* pFrame); + +// ONI_C_API OniStatus oniConvertRealWorldToProjective(OniStreamHandle stream, OniFloatPoint3D* pRealWorldPoint, OniFloatPoint3D* pProjectivePoint); +// ONI_C_API OniStatus oniConvertProjectiveToRealWorld(OniStreamHandle stream, OniFloatPoint3D* pProjectivePoint, OniFloatPoint3D* pRealWorldPoint); + +/** + * Creates a recorder that records to a file. + * @param [in] fileName The name of the file that will contain the recording. + * @param [out] pRecorder Points to the handle to the newly created recorder. + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniCreateRecorder(const char* fileName, OniRecorderHandle* pRecorder); + +/** + * Attaches a stream to a recorder. The amount of attached streams is virtually + * infinite. You cannot attach a stream after you have started a recording, if + * you do: an error will be returned by oniRecorderAttachStream. + * @param [in] recorder The handle to the recorder. + * @param [in] stream The handle to the stream. + * @param [in] allowLossyCompression Allows/denies lossy compression + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniRecorderAttachStream( + OniRecorderHandle recorder, + OniStreamHandle stream, + OniBool allowLossyCompression); + +/** + * Starts recording. There must be at least one stream attached to the recorder, + * if not: oniRecorderStart will return an error. + * @param[in] recorder The handle to the recorder. + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniRecorderStart(OniRecorderHandle recorder); + +/** + * Stops recording. You can resume recording via oniRecorderStart. + * @param[in] recorder The handle to the recorder. + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API void oniRecorderStop(OniRecorderHandle recorder); + +/** + * Stops recording if needed, and destroys a recorder. + * @param [in,out] recorder The handle to the recorder, the handle will be + * invalidated (nullified) when the function returns. + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniRecorderDestroy(OniRecorderHandle* pRecorder); + +ONI_C_API OniStatus oniCoordinateConverterDepthToWorld(OniStreamHandle depthStream, float depthX, float depthY, float depthZ, float* pWorldX, float* pWorldY, float* pWorldZ); + +ONI_C_API OniStatus oniCoordinateConverterWorldToDepth(OniStreamHandle depthStream, float worldX, float worldY, float worldZ, float* pDepthX, float* pDepthY, float* pDepthZ); + +ONI_C_API OniStatus oniCoordinateConverterDepthToColor(OniStreamHandle depthStream, OniStreamHandle colorStream, int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY); + +/******************************************** Log APIs */ + +/** + * Change the log output folder + + * @param const char * strOutputFolder [in] path to the desirebale folder + * + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniSetLogOutputFolder(const char* strOutputFolder); + +/** + * Get the current log file name + + * @param char * strFileName [out] hold the returned file name + * @param int nBufferSize [in] size of strFileName + * + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniGetLogFileName(char* strFileName, int nBufferSize); + +/** + * Set the Minimum severity for log produce + + * @param const char * strMask [in] Name of the logger + * + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniSetLogMinSeverity(int nMinSeverity); + +/** + * Configures if log entries will be printed to console. + + * @param OniBool bConsoleOutput [in] TRUE to print log entries to console, FALSE otherwise. + * + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniSetLogConsoleOutput(OniBool bConsoleOutput); + +/** + * Configures if log entries will be printed to a log file. + + * @param OniBool bFileOutput [in] TRUE to print log entries to the file, FALSE otherwise. + * + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniSetLogFileOutput(OniBool bFileOutput); + +#if ONI_PLATFORM == ONI_PLATFORM_ANDROID_ARM +/** + * Configures if log entries will be printed to the Android log. + + * @param OniBool bAndroidOutput [in] TRUE to print log entries to the Android log, FALSE otherwise. + * + * @retval ONI_STATUS_OK Upon successful completion. + * @retval ONI_STATUS_ERROR Upon any kind of failure. + */ +ONI_C_API OniStatus oniSetLogAndroidOutput(OniBool bAndroidOutput); +#endif +#endif // _ONI_C_API_H_ diff --git a/Include/OniCEnums.h b/Include/OniCEnums.h new file mode 100644 index 0000000..d7f513b --- /dev/null +++ b/Include/OniCEnums.h @@ -0,0 +1,84 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_C_ENUMS_H_ +#define _ONI_C_ENUMS_H_ + +/** Possible failure values */ +typedef enum +{ + ONI_STATUS_OK = 0, + ONI_STATUS_ERROR = 1, + ONI_STATUS_NOT_IMPLEMENTED = 2, + ONI_STATUS_NOT_SUPPORTED = 3, + ONI_STATUS_BAD_PARAMETER = 4, + ONI_STATUS_OUT_OF_FLOW = 5, + ONI_STATUS_NO_DEVICE = 6, + ONI_STATUS_TIME_OUT = 102, +} OniStatus; + +/** The source of the stream */ +typedef enum +{ + ONI_SENSOR_IR = 1, + ONI_SENSOR_COLOR = 2, + ONI_SENSOR_DEPTH = 3, + +} OniSensorType; + +/** All available formats of the output of a stream */ +typedef enum +{ + // Depth + ONI_PIXEL_FORMAT_DEPTH_1_MM = 100, + ONI_PIXEL_FORMAT_DEPTH_100_UM = 101, + ONI_PIXEL_FORMAT_SHIFT_9_2 = 102, + ONI_PIXEL_FORMAT_SHIFT_9_3 = 103, + + // Color + ONI_PIXEL_FORMAT_RGB888 = 200, + ONI_PIXEL_FORMAT_YUV422 = 201, + ONI_PIXEL_FORMAT_GRAY8 = 202, + ONI_PIXEL_FORMAT_GRAY16 = 203, + ONI_PIXEL_FORMAT_JPEG = 204, + ONI_PIXEL_FORMAT_YUYV = 205, +} OniPixelFormat; + +typedef enum +{ + ONI_DEVICE_STATE_OK = 0, + ONI_DEVICE_STATE_ERROR = 1, + ONI_DEVICE_STATE_NOT_READY = 2, + ONI_DEVICE_STATE_EOF = 3 +} OniDeviceState; + +typedef enum +{ + ONI_IMAGE_REGISTRATION_OFF = 0, + ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR = 1, +} OniImageRegistrationMode; + +enum +{ + ONI_TIMEOUT_NONE = 0, + ONI_TIMEOUT_FOREVER = -1, +}; + +#endif // _ONI_C_ENUMS_H_ diff --git a/Include/OniCProperties.h b/Include/OniCProperties.h new file mode 100644 index 0000000..da13d58 --- /dev/null +++ b/Include/OniCProperties.h @@ -0,0 +1,68 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_C_PROPERTIES_H_ +#define _ONI_C_PROPERTIES_H_ + +// Device properties +enum +{ + ONI_DEVICE_PROPERTY_FIRMWARE_VERSION = 0, // By implementation + ONI_DEVICE_PROPERTY_DRIVER_VERSION = 1, // OniVersion + ONI_DEVICE_PROPERTY_HARDWARE_VERSION = 2, // int + ONI_DEVICE_PROPERTY_SERIAL_NUMBER = 3, // string + ONI_DEVICE_PROPERTY_ERROR_STATE = 4, // ?? + ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION = 5, // OniImageRegistrationMode + + // Files + ONI_DEVICE_PROPERTY_PLAYBACK_SPEED = 100, // float + ONI_DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED = 101, // OniBool +}; + +// Stream properties +enum +{ + ONI_STREAM_PROPERTY_CROPPING = 0, // OniCropping* + ONI_STREAM_PROPERTY_HORIZONTAL_FOV = 1, // float: radians + ONI_STREAM_PROPERTY_VERTICAL_FOV = 2, // float: radians + ONI_STREAM_PROPERTY_VIDEO_MODE = 3, // OniVideoMode* + + ONI_STREAM_PROPERTY_MAX_VALUE = 4, // int + ONI_STREAM_PROPERTY_MIN_VALUE = 5, // int + + ONI_STREAM_PROPERTY_STRIDE = 6, // int + ONI_STREAM_PROPERTY_MIRRORING = 7, // OniBool + + ONI_STREAM_PROPERTY_NUMBER_OF_FRAMES = 8, // int + + // Camera + ONI_STREAM_PROPERTY_AUTO_WHITE_BALANCE = 100, // OniBool + ONI_STREAM_PROPERTY_AUTO_EXPOSURE = 101, // OniBool + ONI_STREAM_PROPERTY_EXPOSURE = 102, // int + ONI_STREAM_PROPERTY_GAIN = 103, // int +}; + +// Device commands (for Invoke) +enum +{ + ONI_DEVICE_COMMAND_SEEK = 1, // OniSeek +}; + +#endif // _ONI_C_PROPERTIES_H_ diff --git a/Include/OniCTypes.h b/Include/OniCTypes.h new file mode 100644 index 0000000..1224694 --- /dev/null +++ b/Include/OniCTypes.h @@ -0,0 +1,193 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_TYPES_H_ +#define _ONI_TYPES_H_ + +#include "OniPlatform.h" +#include "OniCEnums.h" + +/** Basic types **/ +typedef int OniBool; + +#ifndef TRUE +#define TRUE 1 +#endif //TRUE +#ifndef FALSE +#define FALSE 0 +#endif //FALSE + +#define ONI_MAX_STR 256 +#define ONI_MAX_SENSORS 10 + +struct OniCallbackHandleImpl; +typedef struct OniCallbackHandleImpl* OniCallbackHandle; + +/** Holds an OpenNI version number, which consists of four separate numbers in the format: @c major.minor.maintenance.build. For example: 2.0.0.20. */ +typedef struct +{ + /** Major version number, incremented for major API restructuring. */ + int major; + /** Minor version number, incremented when significant new features added. */ + int minor; + /** Maintenance build number, incremented for new releases that primarily provide minor bug fixes. */ + int maintenance; + /** Build number. Incremented for each new API build. Generally not shown on the installer and download site. */ + int build; +} OniVersion; + +typedef int OniHardwareVersion; + +/** Description of the output: format and resolution */ +typedef struct +{ + OniPixelFormat pixelFormat; + int resolutionX; + int resolutionY; + int fps; +} OniVideoMode; + +/** List of supported video modes by a specific source */ +typedef struct +{ + OniSensorType sensorType; + int numSupportedVideoModes; + OniVideoMode *pSupportedVideoModes; +} OniSensorInfo; + +/** Basic description of a device */ +typedef struct +{ + char uri[ONI_MAX_STR]; + char vendor[ONI_MAX_STR]; + char name[ONI_MAX_STR]; + uint16_t usbVendorId; + uint16_t usbProductId; +} OniDeviceInfo; + +struct _OniDevice; +typedef _OniDevice* OniDeviceHandle; + +struct _OniStream; +typedef _OniStream* OniStreamHandle; + +struct _OniRecorder; +typedef _OniRecorder* OniRecorderHandle; + +/** All information of the current frame */ +typedef struct +{ + int dataSize; + void* data; + + OniSensorType sensorType; + uint64_t timestamp; + int frameIndex; + + int width; + int height; + + OniVideoMode videoMode; + OniBool croppingEnabled; + int cropOriginX; + int cropOriginY; + + int stride; +} OniFrame; + +typedef void (ONI_CALLBACK_TYPE* OniNewFrameCallback)(OniStreamHandle stream, void* pCookie); +typedef void (ONI_CALLBACK_TYPE* OniGeneralCallback)(void* pCookie); +typedef void (ONI_CALLBACK_TYPE* OniDeviceInfoCallback)(const OniDeviceInfo* pInfo, void* pCookie); +typedef void (ONI_CALLBACK_TYPE* OniDeviceStateCallback)(const OniDeviceInfo* pInfo, OniDeviceState deviceState, void* pCookie); + +typedef void* (ONI_CALLBACK_TYPE* OniFrameAllocBufferCallback)(int size, void* pCookie); +typedef void (ONI_CALLBACK_TYPE* OniFrameFreeBufferCallback)(void* data, void* pCookie); + +typedef struct +{ + OniDeviceInfoCallback deviceConnected; + OniDeviceInfoCallback deviceDisconnected; + OniDeviceStateCallback deviceStateChanged; +} OniDeviceCallbacks; + +typedef struct +{ + int enabled; + int originX; + int originY; + int width; + int height; +} OniCropping; + +// Pixel types +/** +Pixel type used to store depth images. +*/ +typedef uint16_t OniDepthPixel; + +/** +Pixel type used to store 16-bit grayscale images +*/ +typedef uint16_t OniGrayscale16Pixel; + +/** +Pixel type used to store 8-bit grayscale/bayer images +*/ +typedef uint8_t OniGrayscale8Pixel; + +#pragma pack (push, 1) + +/** Holds the value of a single color image pixel in 24-bit RGB format. */ +typedef struct +{ + /* Red value of this pixel. */ + uint8_t r; + /* Green value of this pixel. */ + uint8_t g; + /* Blue value of this pixel. */ + uint8_t b; +} OniRGB888Pixel; + +/** + Holds the value of two pixels in YUV422 format (Luminance/Chrominance,16-bits/pixel). + The first pixel has the values y1, u, v. + The second pixel has the values y2, u, v. +*/ +typedef struct +{ + /** First chrominance value for two pixels, stored as blue luminance difference signal. */ + uint8_t u; + /** Overall luminance value of first pixel. */ + uint8_t y1; + /** Second chrominance value for two pixels, stored as red luminance difference signal. */ + uint8_t v; + /** Overall luminance value of second pixel. */ + uint8_t y2; +} OniYUV422DoublePixel; + +#pragma pack (pop) + +typedef struct +{ + int frameIndex; + OniStreamHandle stream; +} OniSeek; + +#endif // _ONI_TYPES_H_ diff --git a/Include/OniEnums.h b/Include/OniEnums.h new file mode 100644 index 0000000..018f222 --- /dev/null +++ b/Include/OniEnums.h @@ -0,0 +1,86 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_ENUMS_H_ +#define _ONI_ENUMS_H_ + +namespace openni +{ + +/** Possible failure values */ +typedef enum +{ + STATUS_OK = 0, + STATUS_ERROR = 1, + STATUS_NOT_IMPLEMENTED = 2, + STATUS_NOT_SUPPORTED = 3, + STATUS_BAD_PARAMETER = 4, + STATUS_OUT_OF_FLOW = 5, + STATUS_NO_DEVICE = 6, + STATUS_TIME_OUT = 102, +} Status; + +/** The source of the stream */ +typedef enum +{ + SENSOR_IR = 1, + SENSOR_COLOR = 2, + SENSOR_DEPTH = 3, + +} SensorType; + +/** All available formats of the output of a stream */ +typedef enum +{ + // Depth + PIXEL_FORMAT_DEPTH_1_MM = 100, + PIXEL_FORMAT_DEPTH_100_UM = 101, + PIXEL_FORMAT_SHIFT_9_2 = 102, + PIXEL_FORMAT_SHIFT_9_3 = 103, + + // Color + PIXEL_FORMAT_RGB888 = 200, + PIXEL_FORMAT_YUV422 = 201, + PIXEL_FORMAT_GRAY8 = 202, + PIXEL_FORMAT_GRAY16 = 203, + PIXEL_FORMAT_JPEG = 204, + PIXEL_FORMAT_YUYV = 205, +} PixelFormat; + +typedef enum +{ + DEVICE_STATE_OK = 0, + DEVICE_STATE_ERROR = 1, + DEVICE_STATE_NOT_READY = 2, + DEVICE_STATE_EOF = 3 +} DeviceState; + +typedef enum +{ + IMAGE_REGISTRATION_OFF = 0, + IMAGE_REGISTRATION_DEPTH_TO_COLOR = 1, +} ImageRegistrationMode; + +static const int TIMEOUT_NONE = 0; +static const int TIMEOUT_FOREVER = -1; + +} // namespace openni + +#endif // _ONI_ENUMS_H_ diff --git a/Include/OniPlatform.h b/Include/OniPlatform.h new file mode 100644 index 0000000..602b4ba --- /dev/null +++ b/Include/OniPlatform.h @@ -0,0 +1,72 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_PLATFORM_H_ +#define _ONI_PLATFORM_H_ + +// Supported platforms +#define ONI_PLATFORM_WIN32 1 +#define ONI_PLATFORM_LINUX_X86 2 +#define ONI_PLATFORM_LINUX_ARM 3 +#define ONI_PLATFORM_MACOSX 4 +#define ONI_PLATFORM_ANDROID_ARM 5 + +#if (defined _WIN32) +# ifndef RC_INVOKED +# if _MSC_VER < 1300 +# error OpenNI Platform Abstraction Layer - Win32 - Microsoft Visual Studio version below 2003 (7.0) are not supported! +# endif +# endif +# include "Win32/OniPlatformWin32.h" +#elif defined (ANDROID) && defined (__arm__) +# include "Android-Arm/OniPlatformAndroid-Arm.h" +#elif (__linux__ && (i386 || __x86_64__)) +# include "Linux-x86/OniPlatformLinux-x86.h" +#elif (__linux__ && __arm__) +# include "Linux-Arm/OniPlatformLinux-Arm.h" +#elif _ARC +# include "ARC/OniPlaformARC.h" +#elif (__APPLE__) +# include "MacOSX/OniPlatformMacOSX.h" +#else +# error Xiron Platform Abstraction Layer - Unsupported Platform! +#endif + +#ifdef __cplusplus +# define ONI_C extern "C" +# define ONI_C_API_EXPORT ONI_C ONI_API_EXPORT +# define ONI_C_API_IMPORT ONI_C ONI_API_IMPORT +# define ONI_CPP_API_EXPORT ONI_API_EXPORT +# define ONI_CPP_API_IMPORT ONI_API_IMPORT +#else // __cplusplus +# define ONI_C_API_EXPORT ONI_API_EXPORT +# define ONI_C_API_IMPORT ONI_API_IMPORT +#endif // __cplusplus + +#ifdef OPENNI2_EXPORT +# define ONI_C_API ONI_C_API_EXPORT +# define ONI_CPP_API ONI_CPP_API_EXPORT +#else // OPENNI2_EXPORT +# define ONI_C_API ONI_C_API_IMPORT +# define ONI_CPP_API ONI_CPP_API_IMPORT +#endif // OPENNI2_EXPORT + + +#endif // _ONI_PLATFORM_H_ diff --git a/Include/OniProperties.h b/Include/OniProperties.h new file mode 100644 index 0000000..19b0805 --- /dev/null +++ b/Include/OniProperties.h @@ -0,0 +1,73 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_PROPERTIES_H_ +#define _ONI_PROPERTIES_H_ + +namespace openni +{ + +// Device properties +enum +{ + DEVICE_PROPERTY_FIRMWARE_VERSION = 0, // string + DEVICE_PROPERTY_DRIVER_VERSION = 1, // OniVersion + DEVICE_PROPERTY_HARDWARE_VERSION = 2, // int + DEVICE_PROPERTY_SERIAL_NUMBER = 3, // string + DEVICE_PROPERTY_ERROR_STATE = 4, // ?? + DEVICE_PROPERTY_IMAGE_REGISTRATION = 5, // OniImageRegistrationMode + + // Files + DEVICE_PROPERTY_PLAYBACK_SPEED = 100, // float + DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED = 101, // OniBool +}; + +// Stream properties +enum +{ + STREAM_PROPERTY_CROPPING = 0, // OniCropping* + STREAM_PROPERTY_HORIZONTAL_FOV = 1, // float: radians + STREAM_PROPERTY_VERTICAL_FOV = 2, // float: radians + STREAM_PROPERTY_VIDEO_MODE = 3, // OniVideoMode* + + STREAM_PROPERTY_MAX_VALUE = 4, // int + STREAM_PROPERTY_MIN_VALUE = 5, // int + + STREAM_PROPERTY_STRIDE = 6, // int + STREAM_PROPERTY_MIRRORING = 7, // OniBool + + STREAM_PROPERTY_NUMBER_OF_FRAMES = 8, // int + + // Camera + STREAM_PROPERTY_AUTO_WHITE_BALANCE = 100, // OniBool + STREAM_PROPERTY_AUTO_EXPOSURE = 101, // OniBool + STREAM_PROPERTY_EXPOSURE = 102, // int + STREAM_PROPERTY_GAIN = 103, // int + +}; + +// Device commands (for Invoke) +enum +{ + DEVICE_COMMAND_SEEK = 1, // OniSeek +}; + +} // namespace openni +#endif // _ONI_PROPERTIES_H_ diff --git a/Include/OniVersion.h b/Include/OniVersion.h new file mode 100644 index 0000000..7aa13ed --- /dev/null +++ b/Include/OniVersion.h @@ -0,0 +1,43 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "OniPlatform.h" + +#define ONI_VERSION_MAJOR 2 +#define ONI_VERSION_MINOR 2 +#define ONI_VERSION_MAINTENANCE 0 +#define ONI_VERSION_BUILD 33 + +/** OpenNI version (in brief string format): "Major.Minor.Maintenance (Build)" */ +#define ONI_BRIEF_VERSION_STRING \ + ONI_STRINGIFY(ONI_VERSION_MAJOR) "." \ + ONI_STRINGIFY(ONI_VERSION_MINOR) "." \ + ONI_STRINGIFY(ONI_VERSION_MAINTENANCE) \ + " (Build " ONI_STRINGIFY(ONI_VERSION_BUILD) ")" + +/** OpenNI version (in numeric format): (OpenNI major version * 100000000 + OpenNI minor version * 1000000 + OpenNI maintenance version * 10000 + OpenNI build version). */ +#define ONI_VERSION (ONI_VERSION_MAJOR*100000000 + ONI_VERSION_MINOR*1000000 + ONI_VERSION_MAINTENANCE*10000 + ONI_VERSION_BUILD) +#define ONI_CREATE_API_VERSION(major, minor) ((major)*1000 + (minor)) +#define ONI_API_VERSION ONI_CREATE_API_VERSION(ONI_VERSION_MAJOR, ONI_VERSION_MINOR) + +/** OpenNI version (in string format): "Major.Minor.Maintenance.Build-Platform (MMM DD YYYY HH:MM:SS)". */ +#define ONI_VERSION_STRING \ + ONI_BRIEF_VERSION_STRING "-" \ + ONI_PLATFORM_STRING " (" ONI_TIMESTAMP ")" diff --git a/Include/OpenNI.h b/Include/OpenNI.h new file mode 100644 index 0000000..52324b4 --- /dev/null +++ b/Include/OpenNI.h @@ -0,0 +1,2750 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _OPENNI_H_ +#define _OPENNI_H_ + +#include "OniPlatform.h" +#include "OniProperties.h" +#include "OniEnums.h" + +#include "OniCAPI.h" +#include "OniCProperties.h" + +/** +openni is the namespace of the entire C++ API of OpenNI +*/ +namespace openni +{ + +/** Pixel type used to store depth images. */ +typedef uint16_t DepthPixel; + +/** Pixel type used to store IR images. */ +typedef uint16_t Grayscale16Pixel; + +// structs +/** Holds an OpenNI version number, which consists of four separate numbers in the format: @c major.minor.maintenance.build. For example: 2.0.0.20. */ +typedef struct +{ + /** Major version number, incremented for major API restructuring. */ + int major; + /** Minor version number, incremented when significant new features added. */ + int minor; + /** Maintenance build number, incremented for new releases that primarily provide minor bug fixes. */ + int maintenance; + /** Build number. Incremented for each new API build. Generally not shown on the installer and download site. */ + int build; +} Version; + +/** Holds the value of a single color image pixel in 24-bit RGB format. */ +typedef struct +{ + /* Red value of this pixel. */ + uint8_t r; + /* Green value of this pixel. */ + uint8_t g; + /* Blue value of this pixel. */ + uint8_t b; +} RGB888Pixel; + +/** + Holds the value of two pixels in YUV422 format (Luminance/Chrominance,16-bits/pixel). + The first pixel has the values y1, u, v. + The second pixel has the values y2, u, v. +*/ +typedef struct +{ + /** First chrominance value for two pixels, stored as blue luminance difference signal. */ + uint8_t u; + /** Overall luminance value of first pixel. */ + uint8_t y1; + /** Second chrominance value for two pixels, stored as red luminance difference signal. */ + uint8_t v; + /** Overall luminance value of second pixel. */ + uint8_t y2; +} YUV422DoublePixel; + +/** This special URI can be passed to @ref Device::open() when the application has no concern for a specific device. */ +#if ONI_PLATFORM != ONI_PLATFORM_WIN32 +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic push +#endif +static const char* ANY_DEVICE = NULL; +#if ONI_PLATFORM != ONI_PLATFORM_WIN32 +#pragma GCC diagnostic pop +#endif + +/** +Provides a simple array class used throughout the API. Wraps a primitive array +of objects, holding the elements and their count. +*/ +template +class Array +{ +public: + /** + Default constructor. Creates an empty Array and sets the element count to zero. + */ + Array() : m_data(NULL), m_count(0), m_owner(false) {} + + /** + Constructor. Creates new Array from an existing primitive array of known size. + + @tparam [in] T Object type this Array will contain. + @param [in] data Pointer to a primitive array of objects of type T. + @param [in] count Number of elements in the primitive array pointed to by data. + */ + Array(const T* data, int count) : m_owner(false) { _setData(data, count); } + + /** + Destructor. Destroys the Array object. + */ + ~Array() + { + clear(); + } + + /** + Getter function for the Array size. + @returns Current number of elements in the Array. + */ + int getSize() const { return m_count; } + + /** + Implements the array indexing operator for the Array class. + */ + const T& operator[](int index) const {return m_data[index];} + + /** + @internal + Setter function for data. Causes this array to wrap an existing primitive array + of specified type. The optional data ownership flag controls whether the primitive + array this Array wraps will be destroyed when this Array is deconstructed. + @param [in] T Type of objects array will contain. + @param [in] data Pointer to first object in list. + @param [in] count Number of objects in list. + @param [in] isOwner Optional flag to indicate data ownership + */ + void _setData(const T* data, int count, bool isOwner = false) + { + clear(); + m_count = count; + m_owner = isOwner; + if (!isOwner) + { + m_data = data; + } + else + { + m_data = new T[count]; + memcpy((void*)m_data, data, count*sizeof(T)); + } + } + +private: + Array(const Array&); + Array& operator=(const Array&); + + void clear() + { + if (m_owner && m_data != NULL) + delete []m_data; + m_owner = false; + m_data = NULL; + m_count = 0; + } + + const T* m_data; + int m_count; + bool m_owner; +}; + +// Forward declaration of all +class SensorInfo; +class VideoStream; +class VideoFrameRef; +class Device; +class OpenNI; +class CameraSettings; +class PlaybackControl; + +/** +Encapsulates a group of settings for a @ref VideoStream. Settings stored include +frame rate, resolution, and pixel format. + +This class is used as an input for changing the settings of a @ref VideoStream, +as well as an output for reporting the current settings of that class. It is also used +by @ref SensorInfo to report available video modes of a stream. + +Recommended practice is to use @ref SensorInfo::getSupportedVideoModes() +to obtain a list of valid video modes, and then to use items from that list to pass +new settings to @ref VideoStream. This is much less likely to produce an +invalid video mode than instantiating and manually changing objects of this +class. +*/ +class VideoMode : private OniVideoMode +{ +public: + /** + Default constructor, creates an empty VideoMode object. Application programs should, in most + cases, use the copy constructor to copy an existing valid video mode. This is much less + error prone that creating and attempting to configure a new VideoMode from scratch. + */ + VideoMode() + {} + + /** + Copy constructor, creates a new VideoMode identical to an existing VideoMode. + + @param [in] other Existing VideoMode to copy. + */ + VideoMode(const VideoMode& other) + { + *this = other; + } + + /** + Assignment operator. Sets the pixel format, frame rate, and resolution of this + VideoMode to equal that of a different VideoMode. + + @param [in] other Existing VideoMode to copy settings from. + */ + VideoMode& operator=(const VideoMode& other) + { + setPixelFormat(other.getPixelFormat()); + setResolution(other.getResolutionX(), other.getResolutionY()); + setFps(other.getFps()); + + return *this; + } + + /** + Getter function for the pixel format of this VideoMode. + @returns Current pixel format setting of this VideoMode. + */ + PixelFormat getPixelFormat() const { return (PixelFormat)pixelFormat; } + + /** + Getter function for the X resolution of this VideoMode. + @returns Current horizontal resolution of this VideoMode, in pixels. + */ + int getResolutionX() const { return resolutionX; } + + /** + Getter function for the Y resolution of this VideoMode. + @returns Current vertical resolution of this VideoMode, in pixels. + */ + int getResolutionY() const {return resolutionY;} + + /** + Getter function for the frame rate of this VideoMode. + @returns Current frame rate, measured in frames per second. + */ + int getFps() const { return fps; } + + /** + Setter function for the pixel format of this VideoMode. Application use of this + function is not recommended. Instead, use @ref SensorInfo::getSupportedVideoModes() + to obtain a list of valid video modes. + @param [in] format Desired new pixel format for this VideoMode. + */ + void setPixelFormat(PixelFormat format) { this->pixelFormat = (OniPixelFormat)format; } + + /** + Setter function for the resolution of this VideoMode. Application use of this + function is not recommended. Instead, use @ref SensorInfo::getSupportedVideoModes() to + obtain a list of valid video modes. + @param [in] resolutionX Desired new horizontal resolution in pixels. + @param [in] resolutionY Desired new vertical resolution in pixels. + */ + void setResolution(int resolutionX, int resolutionY) + { + this->resolutionX = resolutionX; + this->resolutionY = resolutionY; + } + + /** + Setter function for the frame rate. Application use of this function is not recommended. + Instead, use @ref SensorInfo::getSupportedVideoModes() to obtain a list of valid + video modes. + @param [in] fps Desired new frame rate, measured in frames per second. + */ + void setFps(int fps) { this->fps = fps; } + + friend class SensorInfo; + friend class VideoStream; + friend class VideoFrameRef; +}; + +/** +The SensorInfo class encapsulates all info related to a specific sensor in a specific +device. +A @ref Device object holds a SensorInfo object for each sensor it contains. +A @ref VideoStream object holds one SensorInfo object, describing the sensor used to produce that stream. + +A given SensorInfo object will contain the type of the sensor (Depth, IR or Color), and +a list of all video modes that the sensor can support. Each available video mode will have a single +VideoMode object that can be queried to get the details of that mode. + +SensorInfo objects should be the only source of VideoMode objects for the vast majority of +application programs. + +Application programs will never directly instantiate objects of type SensorInfo. In fact, no +public constructors are provided. SensorInfo objects should be obtained either from a Device or @ref VideoStream, +and in turn be used to provide available video modes for that sensor. +*/ +class SensorInfo +{ +public: + /** + Provides the sensor type of the sensor this object is associated with. + @returns Type of the sensor. + */ + SensorType getSensorType() const { return (SensorType)m_pInfo->sensorType; } + + /** + Provides a list of video modes that this sensor can support. This function is the + recommended method to be used by applications to obtain @ref VideoMode objects. + + @returns Reference to an array of @ref VideoMode objects, one for each supported + video mode. + */ + const Array& getSupportedVideoModes() const { return m_videoModes; } + +private: + SensorInfo(const SensorInfo&); + SensorInfo& operator=(const SensorInfo&); + + SensorInfo() : m_pInfo(NULL), m_videoModes(NULL, 0) {} + + SensorInfo(const OniSensorInfo* pInfo) : m_pInfo(NULL), m_videoModes(NULL, 0) + { + _setInternal(pInfo); + } + + void _setInternal(const OniSensorInfo* pInfo) + { + m_pInfo = pInfo; + if (pInfo == NULL) + { + m_videoModes._setData(NULL, 0); + } + else + { + m_videoModes._setData(static_cast(pInfo->pSupportedVideoModes), pInfo->numSupportedVideoModes); + } + } + + const OniSensorInfo* m_pInfo; + Array m_videoModes; + + friend class VideoStream; + friend class Device; +}; + +/** +The DeviceInfo class encapsulates info related to a specific device. + +Applications will generally obtain objects of this type via calls to @ref OpenNI::enumerateDevices() or +@ref openni::Device::getDeviceInfo(), and then use the various accessor functions to obtain specific +information on that device. + +There should be no reason for application code to instantiate this object directly. +*/ +class DeviceInfo : private OniDeviceInfo +{ +public: + /** + Returns the device URI. URI can be used by @ref Device::open to open a specific device. + The URI string format is determined by the driver. + */ + const char* getUri() const { return uri; } + /** Returns a the vendor name for this device. */ + const char* getVendor() const { return vendor; } + /** Returns the device name for this device. */ + const char* getName() const { return name; } + /** Returns the USB VID code for this device. */ + uint16_t getUsbVendorId() const { return usbVendorId; } + /** Returns the USB PID code for this device. */ + uint16_t getUsbProductId() const { return usbProductId; } + + friend class Device; + friend class OpenNI; +}; + +/** +The @ref VideoFrameRef class encapsulates a single video frame - the output of a @ref VideoStream at a specific time. +The data contained will be a single frame of color, IR, or depth video, along with associated meta data. + +An object of type @ref VideoFrameRef does not actually hold the data of the frame, but only a reference to it. The +reference can be released by destroying the @ref VideoFrameRef object, or by calling the @ref release() method. The +actual data of the frame is freed when the last reference to it is released. + +The usual way to obtain @ref VideoFrameRef objects is by a call to @ref VideoStream.:readFrame(). + +All data references by a @ref VideoFrameRef is stored as a primitive array of pixels. Each pixel will be +of a type according to the configured pixel format (see @ref VideoMode). +*/ +class VideoFrameRef +{ +public: + /** + Default constructor. Creates a new empty @ref VideoFrameRef object. + This object will be invalid until initialized by a call to @ref VideoStream::readFrame(). + */ + VideoFrameRef() + { + m_pFrame = NULL; + } + + /** + Destroy this object and release the reference to the frame. + */ + ~VideoFrameRef() + { + release(); + } + + /** + Copy constructor. Creates a new @ref VideoFrameRef object. The newly created + object will reference the same frame current object references. + @param [in] other Another @ref VideoFrameRef object. + */ + VideoFrameRef(const VideoFrameRef& other) : m_pFrame(NULL) + { + _setFrame(other.m_pFrame); + } + + /** + Make this @ref VideoFrameRef object reference the same frame that the @c other frame references. + If this object referenced another frame before calling this method, the previous frame will be released. + @param [in] other Another @ref VideoFrameRef object. + */ + VideoFrameRef& operator=(const VideoFrameRef& other) + { + _setFrame(other.m_pFrame); + return *this; + } + + /** + Getter function for the size of the data contained by this object. Useful primarily + when allocating buffers. + @returns Current size of data pointed to by this object, measured in bytes. + */ + inline int getDataSize() const + { + return m_pFrame->dataSize; + } + + /** + Getter function for the array of data pointed to by this object. + @returns Pointer to the actual frame data array. Type of data + pointed to can be determined according to the pixel format (can be obtained by calling @ref getVideoMode()). + */ + inline const void* getData() const + { + return m_pFrame->data; + } + + /** + Getter function for the sensor type used to produce this frame. Used to determine whether + this is an IR, Color or Depth frame. See the @ref SensorType enumeration for all possible return + values from this function. + @returns The type of sensor used to produce this frame. + */ + inline SensorType getSensorType() const + { + return (SensorType)m_pFrame->sensorType; + } + + /** + Returns a reference to the @ref VideoMode object assigned to this frame. This object describes + the video mode the sensor was configured to when the frame was produced and can be used + to determine the pixel format and resolution of the data. It will also provide the frame rate + that the sensor was running at when it recorded this frame. + @returns Reference to the @ref VideoMode assigned to this frame. + */ + inline const VideoMode& getVideoMode() const + { + return static_cast(m_pFrame->videoMode); + } + + /** + Provides a timestamp for the frame. The 'zero' point for this stamp + is implementation specific, but all streams from the same device are guaranteed to use the same zero. + This value can therefore be used to compute time deltas between frames from the same device, + regardless of whether they are from the same stream. + @returns Timestamp of frame, measured in microseconds from an arbitrary zero + */ + inline uint64_t getTimestamp() const + { + return m_pFrame->timestamp; + } + + /** + Frames are provided sequential frame ID numbers by the sensor that produced them. If frame + synchronization has been enabled for a device via @ref Device::setDepthColorSyncEnabled(), then frame + numbers for corresponding frames of depth and color are guaranteed to match. + + If frame synchronization is not enabled, then there is no guarantee of matching frame indexes between + @ref VideoStream "VideoStreams". In the latter case, applications should use timestamps instead of frame indexes to + align frames in time. + @returns Index number for this frame. + */ + inline int getFrameIndex() const + { + return m_pFrame->frameIndex; + } + + /** + Gives the current width of this frame, measured in pixels. If cropping is enabled, this will be + the width of the cropping window. If cropping is not enabled, then this will simply be equal to + the X resolution of the @ref VideoMode used to produce this frame. + @returns Width of this frame in pixels. + */ + inline int getWidth() const + { + return m_pFrame->width; + } + + /** + Gives the current height of this frame, measured in pixels. If cropping is enabled, this will + be the length of the cropping window. If cropping is not enabled, then this will simply be equal + to the Y resolution of the @ref VideoMode used to produce this frame. + */ + inline int getHeight() const + { + return m_pFrame->height; + } + + /** + Indicates whether cropping was enabled when the frame was produced. + @return true if cropping is enabled, false otherwise + */ + inline bool getCroppingEnabled() const + { + return m_pFrame->croppingEnabled == TRUE; + } + + /** + Indicates the X coordinate of the upper left corner of the crop window. + @return Distance of crop origin from left side of image, in pixels. + */ + inline int getCropOriginX() const + { + return m_pFrame->cropOriginX; + } + + /** + Indicates the Y coordinate of the upper left corner of the crop window. + @return Distance of crop origin from top of image, in pixels. + */ + inline int getCropOriginY() const + { + return m_pFrame->cropOriginY; + } + + /** + Gives the length of one row of pixels, measured in bytes. Primarily useful + for indexing the array which contains the data. + @returns Stride of the array which contains the image for this frame, in bytes + */ + inline int getStrideInBytes() const + { + return m_pFrame->stride; + } + + /** + Check if this object references an actual frame. + */ + inline bool isValid() const + { + return m_pFrame != NULL; + } + + /** + Release the reference to the frame. Once this method is called, the object becomes invalid, and no method + should be called other than the assignment operator, or passing this object to a @ref VideoStream::readFrame() call. + */ + void release() + { + if (m_pFrame != NULL) + { + oniFrameRelease(m_pFrame); + m_pFrame = NULL; + } + } + + /** @internal */ + void _setFrame(OniFrame* pFrame) + { + setReference(pFrame); + if (pFrame != NULL) + { + oniFrameAddRef(pFrame); + } + } + + /** @internal */ + OniFrame* _getFrame() + { + return m_pFrame; + } + +private: + friend class VideoStream; + inline void setReference(OniFrame* pFrame) + { + // Initial - don't addref. This is the reference from OpenNI + release(); + m_pFrame = pFrame; + } + + OniFrame* m_pFrame; // const!!? +}; + +/** +The @ref VideoStream object encapsulates a single video stream from a device. Once created, it is used to start data flow +from the device, and to read individual frames of data. This is the central class used to obtain data in OpenNI. It +provides the ability to manually read data in a polling loop, as well as providing events and a Listener class that can be +used to implement event-driven data acquisition. + +Aside from the video data frames themselves, the class offers a number of functions used for obtaining information about a +@ref VideoStream. Field of view, available video modes, and minimum and maximum valid pixel values can all be obtained. + +In addition to obtaining data, the @ref VideoStream object is used to set all configuration properties that apply to a specific +stream (rather than to an entire device). In particular, it is used to control cropping, mirroring, and video modes. + +A pointer to a valid, initialized device that provides the desired stream type is required to create a stream. + +Several video streams can be created to stream data from the same sensor. This is useful if several components of an application +need to read frames separately. + +While some device might allow different streams +from the same sensor to have different configurations, most devices will have a single configuration for the sensor, +shared by all streams. +*/ +class VideoStream +{ +public: + /** + The @ref VideoStream::NewFrameListener class is provided to allow the implementation of event driven frame reading. To use + it, create a class that inherits from it and implement override the onNewFrame() method. Then, register + your created class with an active @ref VideoStream using the @ref VideoStream::addNewFrameListener() function. Once this is done, the + event handler function you implemented will be called whenever a new frame becomes available. You may call + @ref VideoStream::readFrame() from within the event handler. + */ + class NewFrameListener + { + public: + /** + Default constructor. + */ + NewFrameListener() : m_callbackHandle(NULL) + { + } + + virtual ~NewFrameListener() + { + } + + /** + Derived classes should implement this function to handle new frames. + */ + virtual void onNewFrame(VideoStream&) = 0; + + private: + friend class VideoStream; + + static void ONI_CALLBACK_TYPE callback(OniStreamHandle streamHandle, void* pCookie) + { + NewFrameListener* pListener = (NewFrameListener*)pCookie; + VideoStream stream; + stream._setHandle(streamHandle); + pListener->onNewFrame(stream); + stream._setHandle(NULL); + } + OniCallbackHandle m_callbackHandle; + }; + + class FrameAllocator + { + public: + virtual ~FrameAllocator() {} + virtual void* allocateFrameBuffer(int size) = 0; + virtual void freeFrameBuffer(void* data) = 0; + + private: + friend class VideoStream; + + static void* ONI_CALLBACK_TYPE allocateFrameBufferCallback(int size, void* pCookie) + { + FrameAllocator* pThis = (FrameAllocator*)pCookie; + return pThis->allocateFrameBuffer(size); + } + + static void ONI_CALLBACK_TYPE freeFrameBufferCallback(void* data, void* pCookie) + { + FrameAllocator* pThis = (FrameAllocator*)pCookie; + pThis->freeFrameBuffer(data); + } + }; + + /** + Default constructor. Creates a new, non-valid @ref VideoStream object. The object created will be invalid until its create() function + is called with a valid Device. + */ + VideoStream() : m_stream(NULL), m_sensorInfo(), m_pCameraSettings(NULL), m_isOwner(true) + {} + + /** + Handle constructor. Creates a VideoStream object based on the given initialized handle. + This object will not destroy the underlying handle when @ref destroy() or destructor is called + */ + explicit VideoStream(OniStreamHandle handle) : m_stream(NULL), m_sensorInfo(), m_pCameraSettings(NULL), m_isOwner(false) + { + _setHandle(handle); + } + + /** + Destructor. The destructor calls the destroy() function, but it is considered a best practice for applications to + call destroy() manually on any @ref VideoStream that they run create() on. + */ + ~VideoStream() + { + destroy(); + } + + /** + Checks to see if this object has been properly initialized and currently points to a valid stream. + @returns true if this object has been previously initialized, false otherwise. + */ + bool isValid() const + { + return m_stream != NULL; + } + + /** + Creates a stream of frames from a specific sensor type of a specific device. You must supply a reference to a + Device that supplies the sensor type requested. You can use @ref Device::hasSensor() to check whether a + given sensor is available on your target device before calling create(). + + @param [in] device A reference to the @ref Device you want to create the stream on. + @param [in] sensorType The type of sensor the stream should produce data from. + @returns Status code indicating success or failure for this operation. + */ + inline Status create(const Device& device, SensorType sensorType); + + /** + Destroy this stream. This function is currently called automatically by the destructor, but it is + considered a best practice for applications to manually call this function on any @ref VideoStream that they + call create() for. + */ + inline void destroy(); + + /** + Provides the @ref SensorInfo object associated with the sensor that is producing this @ref VideoStream. Note that + this function will return NULL if the stream has not yet been initialized with the create() function. + + @ref SensorInfo is useful primarily as a means of learning which video modes are valid for this VideoStream. + + @returns Reference to the SensorInfo object associated with the sensor providing this stream. + */ + const SensorInfo& getSensorInfo() const + { + return m_sensorInfo; + } + + /** + Starts data generation from this video stream. + */ + Status start() + { + if (!isValid()) + { + return STATUS_ERROR; + } + + return (Status)oniStreamStart(m_stream); + } + + /** + Stops data generation from this video stream. + */ + void stop() + { + if (!isValid()) + { + return; + } + + oniStreamStop(m_stream); + } + + /** + Read the next frame from this video stream, delivered as a @ref VideoFrameRef. This is the primary + method for manually obtaining frames of video data. + If no new frame is available, the call will block until one is available. + To avoid blocking, use @ref VideoStream::Listener to implement an event driven architecture. Another + alternative is to use @ref OpenNI::waitForAnyStream() to wait for new frames from several streams. + + @param [out] pFrame Pointer to a @ref VideoFrameRef object to hold the reference to the new frame. + @returns Status code to indicated success or failure of this function. + */ + Status readFrame(VideoFrameRef* pFrame) + { + if (!isValid()) + { + return STATUS_ERROR; + } + + OniFrame* pOniFrame; + Status rc = (Status)oniStreamReadFrame(m_stream, &pOniFrame); + + pFrame->setReference(pOniFrame); + return rc; + } + + /** + Adds a new Listener to receive this VideoStream onNewFrame event. See @ref VideoStream::NewFrameListener for + more information on implementing an event driven frame reading architecture. An instance of a listener can be added to only one source. + + @param [in] pListener Pointer to a @ref VideoStream::NewFrameListener object (or a derivative) that will respond to this event. + @returns Status code indicating success or failure of the operation. + */ + Status addNewFrameListener(NewFrameListener* pListener) + { + if (!isValid()) + { + return STATUS_ERROR; + } + + return (Status)oniStreamRegisterNewFrameCallback(m_stream, pListener->callback, pListener, &pListener->m_callbackHandle); + } + + /** + Removes a Listener from this video stream list. The listener removed will no longer receive new frame events from this stream. + @param [in] pListener Pointer to the listener object to be removed. + */ + void removeNewFrameListener(NewFrameListener* pListener) + { + if (!isValid()) + { + return; + } + + oniStreamUnregisterNewFrameCallback(m_stream, pListener->m_callbackHandle); + pListener->m_callbackHandle = NULL; + } + + /** + Sets the frame buffers allocator for this video stream. + @param [in] pAllocator Pointer to the frame buffers allocator object. Pass NULL to return to default frame allocator. + @returns ONI_STATUS_OUT_OF_FLOW The frame buffers allocator cannot be set while stream is streaming. + */ + Status setFrameBuffersAllocator(FrameAllocator* pAllocator) + { + if (!isValid()) + { + return STATUS_ERROR; + } + + if (pAllocator == NULL) + { + return (Status)oniStreamSetFrameBuffersAllocator(m_stream, NULL, NULL, NULL); + } + else + { + return (Status)oniStreamSetFrameBuffersAllocator(m_stream, pAllocator->allocateFrameBufferCallback, pAllocator->freeFrameBufferCallback, pAllocator); + } + } + + /** + @internal + Get an internal handle. This handle can be used via the C API. + */ + OniStreamHandle _getHandle() const + { + return m_stream; + } + + /** + Gets an object through which several camera settings can be configured. + @returns NULL if the stream doesn't support camera settings. + */ + CameraSettings* getCameraSettings() {return m_pCameraSettings;} + + /** + General function for obtaining the value of stream specific properties. + There are convenience functions available for all commonly used properties, so it is not + expected that applications will make direct use of the getProperty function very often. + + @param [in] propertyId The numerical ID of the property to be queried. + @param [out] data Place to store the value of the property. + @param [in,out] dataSize IN: Size of the buffer passed in the @c data argument. OUT: the actual written size. + @returns Status code indicating success or failure of this operation. + */ + Status getProperty(int propertyId, void* data, int* dataSize) const + { + if (!isValid()) + { + return STATUS_ERROR; + } + + return (Status)oniStreamGetProperty(m_stream, propertyId, data, dataSize); + } + + /** + General function for setting the value of stream specific properties. + There are convenience functions available for all commonly used properties, so it is not + expected that applications will make direct use of the setProperty function very often. + + @param [in] propertyId The numerical ID of the property to be set. + @param [in] data Place to store the data to be written to the property. + @param [in] dataSize Size of the data to be written to the property. + @returns Status code indicating success or failure of this operation. + */ + Status setProperty(int propertyId, const void* data, int dataSize) + { + if (!isValid()) + { + return STATUS_ERROR; + } + + return (Status)oniStreamSetProperty(m_stream, propertyId, data, dataSize); + } + + /** + Get the current video mode information for this video stream. + This includes its resolution, fps and stream format. + + @returns Current video mode information for this video stream. + */ + VideoMode getVideoMode() const + { + VideoMode videoMode; + getProperty(STREAM_PROPERTY_VIDEO_MODE, static_cast(&videoMode)); + return videoMode; + } + + /** + Changes the current video mode of this stream. Recommended practice is to use @ref Device::getSensorInfo(), and + then @ref SensorInfo::getSupportedVideoModes() to obtain a list of valid video mode settings for this stream. Then, + pass a valid @ref VideoMode to @ref setVideoMode to ensure correct operation. + + @param [in] videoMode Desired new video mode for this stream. + returns Status code indicating success or failure of this operation. + */ + Status setVideoMode(const VideoMode& videoMode) + { + return setProperty(STREAM_PROPERTY_VIDEO_MODE, static_cast(videoMode)); + } + + /** + Provides the maximum possible value for pixels obtained by this stream. This is most useful for + getting the maximum possible value of depth streams. + @returns Maximum possible pixel value. + */ + int getMaxPixelValue() const + { + int maxValue; + Status rc = getProperty(STREAM_PROPERTY_MAX_VALUE, &maxValue); + if (rc != STATUS_OK) + { + return 0; + } + return maxValue; + } + + /** + Provides the smallest possible value for pixels obtains by this VideoStream. This is most useful + for getting the minimum possible value that will be reported by a depth stream. + @returns Minimum possible pixel value that can come from this stream. + */ + int getMinPixelValue() const + { + int minValue; + Status rc = getProperty(STREAM_PROPERTY_MIN_VALUE, &minValue); + if (rc != STATUS_OK) + { + return 0; + } + return minValue; + } + + /** + Checks whether this stream supports cropping. + @returns true if the stream supports cropping, false if it does not. + */ + bool isCroppingSupported() const + { + return isPropertySupported(STREAM_PROPERTY_CROPPING); + } + + /** + Obtains the current cropping settings for this stream. + @param [out] pOriginX X coordinate of the upper left corner of the cropping window + @param [out] pOriginY Y coordinate of the upper left corner of the cropping window + @param [out] pWidth Horizontal width of the cropping window, in pixels + @param [out] pHeight Vertical width of the cropping window, in pixels + returns true if cropping is currently enabled, false if it is not. + */ + bool getCropping(int* pOriginX, int* pOriginY, int* pWidth, int* pHeight) const + { + OniCropping cropping; + bool enabled = false; + + Status rc = getProperty(STREAM_PROPERTY_CROPPING, &cropping); + + if (rc == STATUS_OK) + { + *pOriginX = cropping.originX; + *pOriginY = cropping.originY; + *pWidth = cropping.width; + *pHeight = cropping.height; + enabled = (cropping.enabled == TRUE); + } + + return enabled; + } + + /** + Changes the cropping settings for this stream. You can use the @ref isCroppingSupported() + function to make sure cropping is supported before calling this function. + @param [in] originX New X coordinate of the upper left corner of the cropping window. + @param [in] originY New Y coordinate of the upper left corner of the cropping window. + @param [in] width New horizontal width for the cropping window, in pixels. + @param [in] height New vertical height for the cropping window, in pixels. + @returns Status code indicating success or failure of this operation. + */ + Status setCropping(int originX, int originY, int width, int height) + { + OniCropping cropping; + cropping.enabled = true; + cropping.originX = originX; + cropping.originY = originY; + cropping.width = width; + cropping.height = height; + return setProperty(STREAM_PROPERTY_CROPPING, cropping); + } + + /** + Disables cropping. + @returns Status code indicating success or failure of this operation. + */ + Status resetCropping() + { + OniCropping cropping; + cropping.enabled = false; + return setProperty(STREAM_PROPERTY_CROPPING, cropping); + } + + /** + Check whether mirroring is currently turned on for this stream. + @returns true if mirroring is currently enabled, false otherwise. + */ + bool getMirroringEnabled() const + { + OniBool enabled; + Status rc = getProperty(STREAM_PROPERTY_MIRRORING, &enabled); + if (rc != STATUS_OK) + { + return false; + } + return enabled == TRUE; + } + + /** + Enable or disable mirroring for this stream. + @param [in] isEnabled true to enable mirroring, false to disable it. + @returns Status code indicating the success or failure of this operation. + */ + Status setMirroringEnabled(bool isEnabled) + { + return setProperty(STREAM_PROPERTY_MIRRORING, isEnabled ? TRUE : FALSE); + } + + /** + Gets the horizontal field of view of frames received from this stream. + @returns Horizontal field of view, in radians. + */ + float getHorizontalFieldOfView() const + { + float horizontal = 0; + getProperty(STREAM_PROPERTY_HORIZONTAL_FOV, &horizontal); + return horizontal; + } + + /** + Gets the vertical field of view of frames received from this stream. + @returns Vertical field of view, in radians. + */ + float getVerticalFieldOfView() const + { + float vertical = 0; + getProperty(STREAM_PROPERTY_VERTICAL_FOV, &vertical); + return vertical; + } + + /** + Function for setting a value of a stream property using an arbitrary input type. + There are convenience functions available for all commonly used properties, so it is not + expected that applications will make direct use of this function very often. + @tparam [in] T Data type of the value to be passed to the property. + @param [in] propertyId The numerical ID of the property to be set. + @param [in] value Data to be sent to the property. + @returns Status code indicating success or failure of this operation. + */ + template + Status setProperty(int propertyId, const T& value) + { + return setProperty(propertyId, &value, sizeof(T)); + } + + /** + Function for getting the value from a property using an arbitrary output type. + There are convenience functions available for all commonly used properties, so it is not + expected that applications will make direct use of this function very often. + @tparam [in] T Data type of the value to be read. + @param [in] propertyId The numerical ID of the property to be read. + @param [in, out] value Pointer to a place to store the value read from the property. + @returns Status code indicating success or failure of this operation. + */ + template + Status getProperty(int propertyId, T* value) const + { + int size = sizeof(T); + return getProperty(propertyId, value, &size); + } + + /** + Checks if a specific property is supported by the video stream. + @param [in] propertyId Property to be checked. + @returns true if the property is supported, false otherwise. + */ + bool isPropertySupported(int propertyId) const + { + if (!isValid()) + { + return false; + } + + return oniStreamIsPropertySupported(m_stream, propertyId) == TRUE; + } + + /** + Invokes a command that takes an arbitrary data type as its input. It is not expected that + application code will need this function frequently, as all commonly used properties have + higher level functions provided. + @param [in] commandId Numerical code of the property to be invoked. + @param [in] data Data to be passed to the property. + @param [in] dataSize size of the buffer passed in @c data. + @returns Status code indicating success or failure of this operation. + */ + Status invoke(int commandId, void* data, int dataSize) + { + if (!isValid()) + { + return STATUS_ERROR; + } + + return (Status)oniStreamInvoke(m_stream, commandId, data, dataSize); + } + + /** + Invokes a command that takes an arbitrary data type as its input. It is not expected that + application code will need this function frequently, as all commonly used properties have + higher level functions provided. + @tparam [in] T Type of data to be passed to the property. + @param [in] commandId Numerical code of the property to be invoked. + @param [in] value Data to be passed to the property. + @returns Status code indicating success or failure of this operation. + */ + template + Status invoke(int commandId, T& value) + { + return invoke(commandId, &value, sizeof(T)); + } + + /** + Checks if a specific command is supported by the video stream. + @param [in] commandId Command to be checked. + @returns true if the command is supported, false otherwise. + */ + bool isCommandSupported(int commandId) const + { + if (!isValid()) + { + return false; + } + + return (Status)oniStreamIsCommandSupported(m_stream, commandId) == TRUE; + } + +private: + friend class Device; + + void _setHandle(OniStreamHandle stream) + { + m_sensorInfo._setInternal(NULL); + m_stream = stream; + + if (stream != NULL) + { + m_sensorInfo._setInternal(oniStreamGetSensorInfo(m_stream)); + } + } + +private: + VideoStream(const VideoStream& other); + VideoStream& operator=(const VideoStream& other); + + OniStreamHandle m_stream; + SensorInfo m_sensorInfo; + CameraSettings* m_pCameraSettings; + bool m_isOwner; +}; + +/** +The Device object abstracts a specific device; either a single hardware device, or a file +device holding a recording from a hardware device. It offers the ability to connect to +the device, and obtain information about its configuration and the data streams it can offer. + +It provides the means to query and change all configuration parameters that apply to the +device as a whole. This includes enabling depth/color image registration and frame +synchronization. + +Devices are used when creating and initializing @ref VideoStream "VideoStreams" -- you will need a valid pointer to +a Device in order to use the VideoStream.create() function. This, along with configuration, is +the primary use of this class for application developers. + +Before devices can be created, @ref OpenNI::initialize() must have been run to make the device drivers +on the system available to the API. +*/ +class Device +{ +public: + /** + Default constructor. Creates a new empty Device object. This object will be invalid until it is initialized by + calling its open() function. + */ + Device() : m_pPlaybackControl(NULL), m_device(NULL), m_isOwner(true) + { + clearSensors(); + } + + /** + Handle constructor. Creates a Device object based on the given initialized handle. + This object will not destroy the underlying handle when @ref close() or destructor is called + */ + explicit Device(OniDeviceHandle handle) : m_pPlaybackControl(NULL), m_device(NULL), m_isOwner(false) + { + _setHandle(handle); + } + + /** + The destructor calls the @ref close() function, but it is considered a best practice for applications to + call @ref close() manually on any @ref Device that they run @ref open() on. + */ + ~Device() + { + if (m_device != NULL) + { + close(); + } + } + + /** + Opens a device. This can either open a device chosen arbitrarily from all devices + on the system, or open a specific device selected by passing this function the device URI. + + To open any device, simply pass the constant@ref ANY_DEVICE to this function. If multiple + devices are connected to the system, then one of them will be opened. This procedure is most + useful when it is known that exactly one device is (or can be) connected to the system. In that case, + requesting a list of all devices and iterating through it would be a waste of effort. + + If multiple devices are (or may be) connected to a system, then a URI will be required to select + a specific device to open. There are two ways to obtain a URI: from a DeviceConnected event, or + by calling @ref OpenNI::enumerateDevices(). + + In the case of a DeviceConnected event, the @ref OpenNI::Listener will be provided with a DeviceInfo object + as an argument to its @ref OpenNI::Listener::onDeviceConnected "onDeviceConnected()" function. + The DeviceInfo.getUri() function can then be used to obtain the URI. + + If the application is not using event handlers, then it can also call the static function + @ref OpenNI::enumerateDevices(). This will return an array of @ref DeviceInfo objects, one for each device + currently available to the system. The application can then iterate through this list and + select the desired device. The URI is again obtained via the @ref DeviceInfo::getUri() function. + + Standard codes of type Status are returned indicating whether opening was successful. + + @param [in] uri String containing the URI of the device to be opened, or @ref ANY_DEVICE. + @returns Status code with the outcome of the open operation. + + @remark For opening a recording file, pass the file path as a uri. + */ + inline Status open(const char* uri); + + /** + Closes the device. This properly closes any files or shuts down hardware, as appropriate. This + function is currently called by the destructor if not called manually by application code, but it + is considered a best practice to manually close any device that was opened. + */ + inline void close(); + + /** + Provides information about this device in the form of a DeviceInfo object. This object can + be used to access the URI of the device, as well as various USB descriptor strings that might + be useful to an application. + + Note that valid device info will not be available if this device has not yet been opened. If you are + trying to obtain a URI to open a device, use OpenNI::enumerateDevices() instead. + @returns DeviceInfo object for this Device + */ + const DeviceInfo& getDeviceInfo() const + { + return m_deviceInfo; + } + + /** + This function checks to see if one of the specific sensor types defined in @ref SensorType is + available on this device. This allows an application to, for example, query for the presence + of a depth sensor, or color sensor. + @param [in] sensorType of sensor to query for + @returns true if the Device supports the sensor queried, false otherwise. + */ + bool hasSensor(SensorType sensorType) + { + int i; + for (i = 0; (i < ONI_MAX_SENSORS) && (m_aSensorInfo[i].m_pInfo != NULL); ++i) + { + if (m_aSensorInfo[i].getSensorType() == sensorType) + { + return true; + } + } + + if (i == ONI_MAX_SENSORS) + { + return false; + } + + const OniSensorInfo* pInfo = oniDeviceGetSensorInfo(m_device, (OniSensorType)sensorType); + + if (pInfo == NULL) + { + return false; + } + + m_aSensorInfo[i]._setInternal(pInfo); + + return true; + } + + /** + Get the @ref SensorInfo for a specific sensor type on this device. The @ref SensorInfo + is useful primarily for determining which video modes are supported by the sensor. + @param [in] sensorType of sensor to get information about. + @returns SensorInfo object corresponding to the sensor type specified, or NULL if such a sensor + is not available from this device. + */ + const SensorInfo* getSensorInfo(SensorType sensorType) + { + int i; + for (i = 0; (i < ONI_MAX_SENSORS) && (m_aSensorInfo[i].m_pInfo != NULL); ++i) + { + if (m_aSensorInfo[i].getSensorType() == sensorType) + { + return &m_aSensorInfo[i]; + } + } + + // not found. check to see we have additional space + if (i == ONI_MAX_SENSORS) + { + return NULL; + } + + const OniSensorInfo* pInfo = oniDeviceGetSensorInfo(m_device, (OniSensorType)sensorType); + if (pInfo == NULL) + { + return NULL; + } + + m_aSensorInfo[i]._setInternal(pInfo); + return &m_aSensorInfo[i]; + } + + /** + @internal + Get an internal handle. This handle can be used via the C API. + */ + OniDeviceHandle _getHandle() const + { + return m_device; + } + + /** + Gets an object through which playback of a file device can be controlled. + @returns NULL if this device is not a file device. + */ + PlaybackControl* getPlaybackControl() {return m_pPlaybackControl;} + + /** + Get the value of a general property of the device. + There are convenience functions for all the commonly used properties, such as + image registration and frame synchronization. It is expected for this reason + that this function will rarely be directly used by applications. + + @param [in] propertyId Numerical ID of the property you would like to check. + @param [out] data Place to store the value of the property. + @param [in,out] dataSize IN: Size of the buffer passed in the @c data argument. OUT: the actual written size. + @returns Status code indicating results of this operation. + */ + Status getProperty(int propertyId, void* data, int* dataSize) const + { + return (Status)oniDeviceGetProperty(m_device, propertyId, data, dataSize); + } + + /** + Sets the value of a general property of the device. + There are convenience functions for all the commonly used properties, such as + image registration and frame synchronization. It is expected for this reason + that this function will rarely be directly used by applications. + + @param [in] propertyId The numerical ID of the property to be set. + @param [in] data Place to store the data to be written to the property. + @param [in] dataSize Size of the data to be written to the property. + @returns Status code indicating results of this operation. + */ + Status setProperty(int propertyId, const void* data, int dataSize) + { + return (Status)oniDeviceSetProperty(m_device, propertyId, data, dataSize); + } + + /** + Checks to see if this device can support registration of color video and depth video. + Image registration is used to properly superimpose two images from cameras located at different + points in space. Please see the OpenNi 2.0 Programmer's Guide for more information about + registration. + @returns true if image registration is supported by this device, false otherwise. + */ + bool isImageRegistrationModeSupported(ImageRegistrationMode mode) const + { + return (oniDeviceIsImageRegistrationModeSupported(m_device, (OniImageRegistrationMode)mode) == TRUE); + } + + /** + Gets the current image registration mode of this device. + Image registration is used to properly superimpose two images from cameras located at different + points in space. Please see the OpenNi 2.0 Programmer's Guide for more information about + registration. + @returns Current image registration mode. See @ref ImageRegistrationMode for possible return values. + */ + ImageRegistrationMode getImageRegistrationMode() const + { + ImageRegistrationMode mode; + Status rc = getProperty(DEVICE_PROPERTY_IMAGE_REGISTRATION, &mode); + if (rc != STATUS_OK) + { + return IMAGE_REGISTRATION_OFF; + } + return mode; + } + + /** + Sets the image registration on this device. + Image registration is used to properly superimpose two images from cameras located at different + points in space. Please see the OpenNi 2.0 Programmer's Guide for more information about + registration. + + See @ref ImageRegistrationMode for a list of valid settings to pass to this function. + + It is a good practice to first check if the mode is supported by calling @ref isImageRegistrationModeSupported(). + + @param [in] mode Desired new value for the image registration mode. + @returns Status code for the operation. + */ + Status setImageRegistrationMode(ImageRegistrationMode mode) + { + return setProperty(DEVICE_PROPERTY_IMAGE_REGISTRATION, mode); + } + + /** + Checks whether this Device object is currently connected to an actual file or hardware device. + @returns true if the Device is connected, false otherwise. + */ + bool isValid() const + { + return m_device != NULL; + } + + /** + Checks whether this device is a file device (i.e. a recording). + @returns true if this is a file device, false otherwise. + */ + bool isFile() const + { + return isPropertySupported(DEVICE_PROPERTY_PLAYBACK_SPEED) && + isPropertySupported(DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED) && + isCommandSupported(DEVICE_COMMAND_SEEK); + } + + /** + Used to turn the depth/color frame synchronization feature on and off. When frame synchronization + is enabled, the device will deliver depth and image frames that are separated in time + by some maximum value. When disabled, the phase difference between depth and image frame + generation cannot be guaranteed. + @param [in] isEnabled Set to TRUE to enable synchronization, FALSE to disable it + @returns Status code indicating success or failure of this operation + */ + Status setDepthColorSyncEnabled(bool isEnabled) + { + Status rc = STATUS_OK; + + if (isEnabled) + { + rc = (Status)oniDeviceEnableDepthColorSync(m_device); + } + else + { + oniDeviceDisableDepthColorSync(m_device); + } + + return rc; + } + + bool getDepthColorSyncEnabled() + { + return oniDeviceGetDepthColorSyncEnabled(m_device) == TRUE; + } + + /** + Sets a property that takes an arbitrary data type as its input. It is not expected that + application code will need this function frequently, as all commonly used properties have + higher level functions provided. + + @tparam T Type of data to be passed to the property. + @param [in] propertyId The numerical ID of the property to be set. + @param [in] value Place to store the data to be written to the property. + @returns Status code indicating success or failure of this operation. + */ + template + Status setProperty(int propertyId, const T& value) + { + return setProperty(propertyId, &value, sizeof(T)); + } + + /** + Checks a property that provides an arbitrary data type as its output. It is not expected that + application code will need this function frequently, as all commonly used properties have + higher level functions provided. + @tparam [in] T Data type of the value to be read. + @param [in] propertyId The numerical ID of the property to be read. + @param [in, out] value Pointer to a place to store the value read from the property. + @returns Status code indicating success or failure of this operation. + */ + template + Status getProperty(int propertyId, T* value) const + { + int size = sizeof(T); + return getProperty(propertyId, value, &size); + } + + /** + Checks if a specific property is supported by the device. + @param [in] propertyId Property to be checked. + @returns true if the property is supported, false otherwise. + */ + bool isPropertySupported(int propertyId) const + { + return oniDeviceIsPropertySupported(m_device, propertyId) == TRUE; + } + + /** + Invokes a command that takes an arbitrary data type as its input. It is not expected that + application code will need this function frequently, as all commonly used properties have + higher level functions provided. + @param [in] commandId Numerical code of the property to be invoked. + @param [in] data Data to be passed to the property. + @param [in] dataSize size of the buffer passed in @c data. + @returns Status code indicating success or failure of this operation. + */ + Status invoke(int commandId, void* data, int dataSize) + { + return (Status)oniDeviceInvoke(m_device, commandId, data, dataSize); + } + + /** + Invokes a command that takes an arbitrary data type as its input. It is not expected that + application code will need this function frequently, as all commonly used properties have + higher level functions provided. + @tparam [in] T Type of data to be passed to the property. + @param [in] propertyId Numerical code of the property to be invoked. + @param [in] value Data to be passed to the property. + @returns Status code indicating success or failure of this operation. + */ + template + Status invoke(int propertyId, T& value) + { + return invoke(propertyId, &value, sizeof(T)); + } + + /** + Checks if a specific command is supported by the device. + @param [in] commandId Command to be checked. + @returns true if the command is supported, false otherwise. + */ + bool isCommandSupported(int commandId) const + { + return oniDeviceIsCommandSupported(m_device, commandId) == TRUE; + } + + /** @internal **/ + inline Status _openEx(const char* uri, const char* mode); + +private: + Device(const Device&); + Device& operator=(const Device&); + + void clearSensors() + { + for (int i = 0; i < ONI_MAX_SENSORS; ++i) + { + m_aSensorInfo[i]._setInternal(NULL); + } + } + + inline Status _setHandle(OniDeviceHandle deviceHandle); + +private: + PlaybackControl* m_pPlaybackControl; + + OniDeviceHandle m_device; + DeviceInfo m_deviceInfo; + SensorInfo m_aSensorInfo[ONI_MAX_SENSORS]; + + bool m_isOwner; +}; + +/** + * The PlaybackControl class provides access to a series of specific to playing back + * a recording from a file device. + * + * When playing a stream back from a recording instead of playing from a live device, + * it is possible to vary playback speed, change the current time location (ie + * fast forward / rewind / seek), specify whether the playback should be repeated at the end + * of the recording, and query the total size of the recording. + * + * Since none of these functions make sense in the context of a physical device, they are + * split out into a seperate playback control class. To use, simply create your file device, + * create a PlaybackControl, and then attach the PlaybackControl to the file device. + */ +class PlaybackControl +{ +public: + + /** + * Deconstructor. Destroys a PlaybackControl class. The deconstructor presently detaches + * from its recording automatically, but it is considered a best practice for applications to + * manually detach from any stream that was attached to. + */ + ~PlaybackControl() + { + detach(); + } + + /** + * Getter function for the current playback speed of this device. + * + * This value is expressed as a multiple of the speed the original + * recording was taken at. For example, if the original recording was at 30fps, and + * playback speed is set to 0.5, then the recording will play at 15fps. If playback speed + * is set to 2.0, then the recording would playback at 60fps. + * + * In addition, there are two "special" values. A playback speed of 0.0 indicates that the + * playback should occur as fast as the system is capable of returning frames. This is + * most useful when testing algorithms on large datasets, as it enables playback to be + * done at a much higher rate than would otherwise be possible. + * + * A value of -1 indicates that speed is "manual". In this mode, new frames will only + * become available when an application manually reads them. If used in a polling loop, + * this setting also enables systems to read and process frames limited only by + * available processing speeds. + * + * @returns Current playback speed of the device, measured as ratio of recording speed. + */ + float getSpeed() const + { + if (!isValid()) + { + return 0.0f; + } + float speed; + Status rc = m_pDevice->getProperty(DEVICE_PROPERTY_PLAYBACK_SPEED, &speed); + if (rc != STATUS_OK) + { + return 1.0f; + } + return speed; + } + /** + * Setter function for the playback speed of the device. For a full explaination of + * what this value means @see PlaybackControl::getSpeed(). + * + * @param [in] speed Desired new value of playback speed, as ratio of original recording. + * @returns Status code indicating success or failure of this operation. + */ + Status setSpeed(float speed) + { + if (!isValid()) + { + return STATUS_NO_DEVICE; + } + return m_pDevice->setProperty(DEVICE_PROPERTY_PLAYBACK_SPEED, speed); + } + + /** + * Gets the current repeat setting of the file device. + * + * @returns true if repeat is enabled, false if not enabled. + */ + bool getRepeatEnabled() const + { + if (!isValid()) + { + return false; + } + + OniBool repeat; + Status rc = m_pDevice->getProperty(DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED, &repeat); + if (rc != STATUS_OK) + { + return false; + } + + return repeat == TRUE; + } + + /** + * Changes the current repeat mode of the device. If repeat mode is turned on, then the recording will + * begin playback again at the beginning after the last frame is read. If turned off, no more frames + * will become available after last frame is read. + * + * @param [in] repeat New value for repeat -- true to enable, false to disable + * @returns Status code indicating success or failure of this operations. + */ + Status setRepeatEnabled(bool repeat) + { + if (!isValid()) + { + return STATUS_NO_DEVICE; + } + + return m_pDevice->setProperty(DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED, repeat ? TRUE : FALSE); + } + + /** + * Seeks within a VideoStream to a given FrameID. Note that when this function is called on one + * stream, all other streams will also be changed to the corresponding place in the recording. The FrameIDs + * of different streams may not match, since FrameIDs may differ for streams that are not synchronized, but + * the recording will set all streams to the same moment in time. + * + * @param [in] stream Stream for which the frameIndex value is valid. + * @param [in] frameIndex Frame index to move playback to + * @returns Status code indicating success or failure of this operation + */ + Status seek(const VideoStream& stream, int frameIndex) + { + if (!isValid()) + { + return STATUS_NO_DEVICE; + } + OniSeek seek; + seek.frameIndex = frameIndex; + seek.stream = stream._getHandle(); + return m_pDevice->invoke(DEVICE_COMMAND_SEEK, seek); + } + + /** + * Provides the a count of frames that this recording contains for a given stream. This is useful + * both to determine the length of the recording, and to ensure that a valid Frame Index is set when using + * the @ref PlaybackControl::seek() function. + * + * @param [in] stream The video stream to count frames for + * @returns Number of frames in provided @ref VideoStream, or 0 if the stream is not part of the recording + */ + int getNumberOfFrames(const VideoStream& stream) const + { + int numOfFrames = -1; + Status rc = stream.getProperty(STREAM_PROPERTY_NUMBER_OF_FRAMES, &numOfFrames); + if (rc != STATUS_OK) + { + return 0; + } + return numOfFrames; + } + + bool isValid() const + { + return m_pDevice != NULL; + } +private: + Status attach(Device* device) + { + if (!device->isValid() || !device->isFile()) + { + return STATUS_ERROR; + } + + detach(); + m_pDevice = device; + + return STATUS_OK; + } + void detach() + { + m_pDevice = NULL; + } + + friend class Device; + PlaybackControl(Device* pDevice) : m_pDevice(NULL) + { + if (pDevice != NULL) + { + attach(pDevice); + } + } + + Device* m_pDevice; +}; + +class CameraSettings +{ +public: + // setters + Status setAutoExposureEnabled(bool enabled) + { + return setProperty(STREAM_PROPERTY_AUTO_EXPOSURE, enabled ? TRUE : FALSE); + } + Status setAutoWhiteBalanceEnabled(bool enabled) + { + return setProperty(STREAM_PROPERTY_AUTO_WHITE_BALANCE, enabled ? TRUE : FALSE); + } + + bool getAutoExposureEnabled() const + { + OniBool enabled = FALSE; + + Status rc = getProperty(STREAM_PROPERTY_AUTO_EXPOSURE, &enabled); + return rc == STATUS_OK && enabled == TRUE; + } + bool getAutoWhiteBalanceEnabled() const + { + OniBool enabled = FALSE; + + Status rc = getProperty(STREAM_PROPERTY_AUTO_WHITE_BALANCE, &enabled); + return rc == STATUS_OK && enabled == TRUE; + } + + Status setGain(int gain) + { + return setProperty(STREAM_PROPERTY_GAIN, gain); + } + Status setExposure(int exposure) + { + return setProperty(STREAM_PROPERTY_EXPOSURE, exposure); + } + int getGain() + { + int gain; + Status rc = getProperty(STREAM_PROPERTY_GAIN, &gain); + if (rc != STATUS_OK) + { + return 100; + } + return gain; + } + int getExposure() + { + int exposure; + Status rc = getProperty(STREAM_PROPERTY_EXPOSURE, &exposure); + if (rc != STATUS_OK) + { + return 0; + } + return exposure; + } + + bool isValid() const {return m_pStream != NULL;} +private: + template + Status getProperty(int propertyId, T* value) const + { + if (!isValid()) return STATUS_NOT_SUPPORTED; + + return m_pStream->getProperty(propertyId, value); + } + template + Status setProperty(int propertyId, const T& value) + { + if (!isValid()) return STATUS_NOT_SUPPORTED; + + return m_pStream->setProperty(propertyId, value); + } + + friend class VideoStream; + CameraSettings(VideoStream* pStream) + { + m_pStream = pStream; + } + + VideoStream* m_pStream; +}; + + +/** + * The OpenNI class is a static entry point to the library. It is used by every OpenNI 2.0 + * application to initialize the SDK and drivers to enable creation of valid device objects. + * + * It also defines a listener class and events that enable for event driven notification of + * device connection, device disconnection, and device configuration changes. + * + * In addition, it gives access to SDK version information and provides a function that allows + * you to wait for data to become available on any one of a list of streams (as opposed to + * waiting for data on one specific stream with functions provided by the VideoStream class) + * +*/ +class OpenNI +{ +public: + + /** + * The OpenNI::DeviceConnectedListener class provides a means of registering for, and responding to + * when a device is connected. + * + * onDeviceConnected is called whenever a new device is connected to the system (ie this event + * would be triggered when a new sensor is manually plugged into the host system running the + * application) + * + * To use this class, you should write a new class that inherits from it, and override the + * onDeviceConnected method. Once you instantiate your class, use the + * OpenNI::addDeviceConnectedListener() function to add your listener object to OpenNI's list of listeners. Your + * handler function will then be called whenever the event occurs. A OpenNI::removeDeviceConnectedListener() + * function is also provided, if you want to have your class stop listening to these events for any + * reason. + */ + class DeviceConnectedListener + { + public: + DeviceConnectedListener() + { + m_deviceConnectedCallbacks.deviceConnected = deviceConnectedCallback; + m_deviceConnectedCallbacks.deviceDisconnected = NULL; + m_deviceConnectedCallbacks.deviceStateChanged = NULL; + m_deviceConnectedCallbacksHandle = NULL; + } + + virtual ~DeviceConnectedListener() + { + } + + /** + * Callback function for the onDeviceConnected event. This function will be + * called whenever this event occurs. When this happens, a pointer to the @ref DeviceInfo + * object for the newly connected device will be supplied. Note that once a + * device is removed, if it was opened by a @ref Device object, that object can no longer be + * used to access the device, even if it was reconnected. Once a device was reconnected, + * @ref Device::open() should be called again in order to use this device. + * + * If you wish to open the new device as it is connected, simply query the provided DeviceInfo + * object to obtain the URI of the device, and pass this URI to the Device.Open() function. + */ + virtual void onDeviceConnected(const DeviceInfo*) = 0; + private: + static void ONI_CALLBACK_TYPE deviceConnectedCallback(const OniDeviceInfo* pInfo, void* pCookie) + { + DeviceConnectedListener* pListener = (DeviceConnectedListener*)pCookie; + pListener->onDeviceConnected(static_cast(pInfo)); + } + + friend class OpenNI; + OniDeviceCallbacks m_deviceConnectedCallbacks; + OniCallbackHandle m_deviceConnectedCallbacksHandle; + + }; + /** + * The OpenNI::DeviceDisconnectedListener class provides a means of registering for, and responding to + * when a device is disconnected. + * + * onDeviceDisconnected is called when a device is removed from the system. Note that once a + * device is removed, if it was opened by a @ref Device object, that object can no longer be + * used to access the device, even if it was reconnected. Once a device was reconnected, + * @ref Device::open() should be called again in order to use this device. + * + * To use this class, you should write a new class that inherits from it, and override the + * onDeviceDisconnected method. Once you instantiate your class, use the + * OpenNI::addDeviceDisconnectedListener() function to add your listener object to OpenNI's list of listeners. Your + * handler function will then be called whenever the event occurs. A OpenNI::removeDeviceDisconnectedListener() + * function is also provided, if you want to have your class stop listening to these events for any + * reason. + */ + class DeviceDisconnectedListener + { + public: + DeviceDisconnectedListener() + { + m_deviceDisconnectedCallbacks.deviceConnected = NULL; + m_deviceDisconnectedCallbacks.deviceDisconnected = deviceDisconnectedCallback; + m_deviceDisconnectedCallbacks.deviceStateChanged = NULL; + m_deviceDisconnectedCallbacksHandle = NULL; + } + + virtual ~DeviceDisconnectedListener() + { + } + + /** + * Callback function for the onDeviceDisconnected event. This function will be + * called whenever this event occurs. When this happens, a pointer to the DeviceInfo + * object for the newly disconnected device will be supplied. Note that once a + * device is removed, if it was opened by a @ref Device object, that object can no longer be + * used to access the device, even if it was reconnected. Once a device was reconnected, + * @ref Device::open() should be called again in order to use this device. + */ + virtual void onDeviceDisconnected(const DeviceInfo*) = 0; + private: + static void ONI_CALLBACK_TYPE deviceDisconnectedCallback(const OniDeviceInfo* pInfo, void* pCookie) + { + DeviceDisconnectedListener* pListener = (DeviceDisconnectedListener*)pCookie; + pListener->onDeviceDisconnected(static_cast(pInfo)); + } + + friend class OpenNI; + OniDeviceCallbacks m_deviceDisconnectedCallbacks; + OniCallbackHandle m_deviceDisconnectedCallbacksHandle; + }; + /** + * The OpenNI::DeviceStateChangedListener class provides a means of registering for, and responding to + * when a device's state is changed. + * + * onDeviceStateChanged is triggered whenever the state of a connected device is changed. + * + * To use this class, you should write a new class that inherits from it, and override the + * onDeviceStateChanged method. Once you instantiate your class, use the + * OpenNI::addDeviceStateChangedListener() function to add your listener object to OpenNI's list of listeners. Your + * handler function will then be called whenever the event occurs. A OpenNI::removeDeviceStateChangedListener() + * function is also provided, if you want to have your class stop listening to these events for any + * reason. + */ + class DeviceStateChangedListener + { + public: + DeviceStateChangedListener() + { + m_deviceStateChangedCallbacks.deviceConnected = NULL; + m_deviceStateChangedCallbacks.deviceDisconnected = NULL; + m_deviceStateChangedCallbacks.deviceStateChanged = deviceStateChangedCallback; + m_deviceStateChangedCallbacksHandle = NULL; + } + + virtual ~DeviceStateChangedListener() + { + } + + /** + * Callback function for the onDeviceStateChanged event. This function will be + * called whenever this event occurs. When this happens, a pointer to a DeviceInfo + * object for the affected device will be supplied, as well as the new DeviceState + * value of that device. + */ + virtual void onDeviceStateChanged(const DeviceInfo*, DeviceState) = 0; + private: + static void ONI_CALLBACK_TYPE deviceStateChangedCallback(const OniDeviceInfo* pInfo, OniDeviceState state, void* pCookie) + { + DeviceStateChangedListener* pListener = (DeviceStateChangedListener*)pCookie; + pListener->onDeviceStateChanged(static_cast(pInfo), DeviceState(state)); + } + + friend class OpenNI; + OniDeviceCallbacks m_deviceStateChangedCallbacks; + OniCallbackHandle m_deviceStateChangedCallbacksHandle; + }; + + /** + Initialize the library. + This will load all available drivers, and see which devices are available + It is forbidden to call any other method in OpenNI before calling @ref initialize(). + */ + static Status initialize() + { + return (Status)oniInitialize(ONI_API_VERSION); // provide version of API, to make sure proper struct sizes are used + } + + /** + Stop using the library. Unload all drivers, close all streams and devices. + Once @ref shutdown was called, no other calls to OpenNI is allowed. + */ + static void shutdown() + { + oniShutdown(); + } + + /** + * Returns the version of OpenNI + */ + static Version getVersion() + { + OniVersion oniVersion = oniGetVersion(); + Version version; + version.major = oniVersion.major; + version.minor = oniVersion.minor; + version.maintenance = oniVersion.maintenance; + version.build = oniVersion.build; + return version; + } + + /** + * Retrieves the calling thread's last extended error information. The last extended error information is maintained + * on a per-thread basis. Multiple threads do not overwrite each other's last extended error information. + * + * The extended error information is cleared on every call to an OpenNI method, so you should call this method + * immediately after a call to an OpenNI method which have failed. + */ + static const char* getExtendedError() + { + return oniGetExtendedError(); + } + + /** + Fills up an array of @ref DeviceInfo objects with devices that are available. + @param [in,out] deviceInfoList An array to be filled with devices. + */ + static void enumerateDevices(Array* deviceInfoList) + { + OniDeviceInfo* m_pDeviceInfos; + int m_deviceInfoCount; + oniGetDeviceList(&m_pDeviceInfos, &m_deviceInfoCount); + deviceInfoList->_setData((DeviceInfo*)m_pDeviceInfos, m_deviceInfoCount, true); + oniReleaseDeviceList(m_pDeviceInfos); + } + + /** + Wait for a new frame from any of the streams provided. The function blocks until any of the streams + has a new frame available, or the timeout has passed. + @param [in] pStreams An array of streams to wait for. + @param [in] streamCount The number of streams in @c pStreams + @param [out] pReadyStreamIndex The index of the first stream that has new frame available. + @param [in] timeout [Optional] A timeout before returning if no stream has new data. Default value is @ref TIMEOUT_FOREVER. + */ + static Status waitForAnyStream(VideoStream** pStreams, int streamCount, int* pReadyStreamIndex, int timeout = TIMEOUT_FOREVER) + { + static const int ONI_MAX_STREAMS = 50; + OniStreamHandle streams[ONI_MAX_STREAMS]; + + if (streamCount > ONI_MAX_STREAMS) + { + printf("Too many streams for wait: %d > %d\n", streamCount, ONI_MAX_STREAMS); + return STATUS_BAD_PARAMETER; + } + + *pReadyStreamIndex = -1; + for (int i = 0; i < streamCount; ++i) + { + if (pStreams[i] != NULL) + { + streams[i] = pStreams[i]->_getHandle(); + } + else + { + streams[i] = NULL; + } + } + Status rc = (Status)oniWaitForAnyStream(streams, streamCount, pReadyStreamIndex, timeout); + + return rc; + } + + /** + * Add a listener to the list of objects that receive the event when a device is connected. See the + * @ref OpenNI::DeviceConnectedListener class for details on utilizing the events provided by OpenNI. + * + * @param pListener Pointer to the Listener to be added to the list + * @returns Status code indicating success or failure of this operation. + */ + static Status addDeviceConnectedListener(DeviceConnectedListener* pListener) + { + if (pListener->m_deviceConnectedCallbacksHandle != NULL) + { + return STATUS_ERROR; + } + return (Status)oniRegisterDeviceCallbacks(&pListener->m_deviceConnectedCallbacks, pListener, &pListener->m_deviceConnectedCallbacksHandle); + } + /** + * Add a listener to the list of objects that receive the event when a device is disconnected. See the + * @ref OpenNI::DeviceDisconnectedListener class for details on utilizing the events provided by OpenNI. + * + * @param pListener Pointer to the Listener to be added to the list + * @returns Status code indicating success or failure of this operation. + */ + static Status addDeviceDisconnectedListener(DeviceDisconnectedListener* pListener) + { + if (pListener->m_deviceDisconnectedCallbacksHandle != NULL) + { + return STATUS_ERROR; + } + return (Status)oniRegisterDeviceCallbacks(&pListener->m_deviceDisconnectedCallbacks, pListener, &pListener->m_deviceDisconnectedCallbacksHandle); + } + /** + * Add a listener to the list of objects that receive the event when a device's state changes. See the + * @ref OpenNI::DeviceStateChangedListener class for details on utilizing the events provided by OpenNI. + * + * @param pListener Pointer to the Listener to be added to the list + * @returns Status code indicating success or failure of this operation. + */ + static Status addDeviceStateChangedListener(DeviceStateChangedListener* pListener) + { + if (pListener->m_deviceStateChangedCallbacksHandle != NULL) + { + return STATUS_ERROR; + } + return (Status)oniRegisterDeviceCallbacks(&pListener->m_deviceStateChangedCallbacks, pListener, &pListener->m_deviceStateChangedCallbacksHandle); + } + /** + * Remove a listener from the list of objects that receive the event when a device is connected. See + * the @ref OpenNI::DeviceConnectedListener class for details on utilizing the events provided by OpenNI. + * + * @param pListener Pointer to the Listener to be removed from the list + * @returns Status code indicating the success or failure of this operation. + */ + static void removeDeviceConnectedListener(DeviceConnectedListener* pListener) + { + oniUnregisterDeviceCallbacks(pListener->m_deviceConnectedCallbacksHandle); + pListener->m_deviceConnectedCallbacksHandle = NULL; + } + /** + * Remove a listener from the list of objects that receive the event when a device is disconnected. See + * the @ref OpenNI::DeviceDisconnectedListener class for details on utilizing the events provided by OpenNI. + * + * @param pListener Pointer to the Listener to be removed from the list + * @returns Status code indicating the success or failure of this operation. + */ + static void removeDeviceDisconnectedListener(DeviceDisconnectedListener* pListener) + { + oniUnregisterDeviceCallbacks(pListener->m_deviceDisconnectedCallbacksHandle); + pListener->m_deviceDisconnectedCallbacksHandle = NULL; + } + /** + * Remove a listener from the list of objects that receive the event when a device's state changes. See + * the @ref OpenNI::DeviceStateChangedListener class for details on utilizing the events provided by OpenNI. + * + * @param pListener Pointer to the Listener to be removed from the list + * @returns Status code indicating the success or failure of this operation. + */ + static void removeDeviceStateChangedListener(DeviceStateChangedListener* pListener) + { + oniUnregisterDeviceCallbacks(pListener->m_deviceStateChangedCallbacksHandle); + pListener->m_deviceStateChangedCallbacksHandle = NULL; + } + + /** + * Change the log output folder + + * @param const char * strLogOutputFolder [in] log required folder + * + * @retval STATUS_OK Upon successful completion. + * @retval STATUS_ERROR Upon any kind of failure. + */ + static Status setLogOutputFolder(const char *strLogOutputFolder) + { + return (Status)oniSetLogOutputFolder(strLogOutputFolder); + } + + /** + * Get current log file name + + * @param char * strFileName [out] returned file name buffer + * @param int nBufferSize [in] Buffer size + * + * @retval STATUS_OK Upon successful completion. + * @retval STATUS_ERROR Upon any kind of failure. + */ + static Status getLogFileName(char *strFileName, int nBufferSize) + { + return (Status)oniGetLogFileName(strFileName, nBufferSize); + } + + /** + * Set minimum severity for log produce + + * @param const char * strMask [in] Logger name + * @param int nMinSeverity [in] Logger severity + * + * @retval STATUS_OK Upon successful completion. + * @retval STATUS_ERROR Upon any kind of failure. + */ + static Status setLogMinSeverity(int nMinSeverity) + { + return(Status) oniSetLogMinSeverity(nMinSeverity); + } + + /** + * Configures if log entries will be printed to console. + + * @param const OniBool bConsoleOutput [in] TRUE to print log entries to console, FALSE otherwise. + * + * @retval STATUS_OK Upon successful completion. + * @retval STATUS_ERROR Upon any kind of failure. + */ + static Status setLogConsoleOutput(bool bConsoleOutput) + { + return (Status)oniSetLogConsoleOutput(bConsoleOutput); + } + + /** + * Configures if log entries will be printed to file. + + * @param const OniBool bConsoleOutput [in] TRUE to print log entries to file, FALSE otherwise. + * + * @retval STATUS_OK Upon successful completion. + * @retval STATUS_ERROR Upon any kind of failure. + */ + static Status setLogFileOutput(bool bFileOutput) + { + return (Status)oniSetLogFileOutput(bFileOutput); + } + + #if ONI_PLATFORM == ONI_PLATFORM_ANDROID_ARM + /** + * Configures if log entries will be printed to the Android log. + + * @param OniBool bAndroidOutput bAndroidOutput [in] TRUE to print log entries to the Android log, FALSE otherwise. + * + * @retval STATUS_OK Upon successful completion. + * @retval STATUS_ERROR Upon any kind of failure. + */ + + static Status setLogAndroidOutput(bool bAndroidOutput) + { + return (Status)oniSetLogAndroidOutput(bAndroidOutput); + } + #endif + +private: + OpenNI() + { + } +}; + +/** +The CoordinateConverter class converts points between the different coordinate systems. + +Depth and World coordinate systems + +OpenNI applications commonly use two different coordinate systems to represent depth. These two systems are referred to as Depth +and World representation. + +Depth coordinates are the native data representation. In this system, the frame is a map (two dimensional array), and each pixel is +assigned a depth value. This depth value represents the distance between the camera plane and whatever object is in the given +pixel. The X and Y coordinates are simply the location in the map, where the origin is the top-left corner of the field of view. + +World coordinates superimpose a more familiar 3D Cartesian coordinate system on the world, with the camera lens at the origin. +In this system, every point is specified by 3 points -- x, y and z. The x axis of this system is along a line that passes +through the infrared projector and CMOS imager of the camera. The y axis is parallel to the front face of the camera, and +perpendicular to the x axis (it will also be perpendicular to the ground if the camera is upright and level). The z axis +runs into the scene, perpendicular to both the x and y axis. From the perspective of the camera, an object moving from +left to right is moving along the increasing x axis. An object moving up is moving along the increasing y axis, and an object +moving away from the camera is moving along the increasing z axis. + +Mathematically, the Depth coordinate system is the projection of the scene on the CMOS. If the sensor's angular field of view and +resolution are known, then an angular size can be calculated for each pixel. This is how the conversion algorithms work. The +dependence of this calculation on FoV and resolution is the reason that a @ref VideoStream pointer must be provided to these +functions. The @ref VideoStream pointer is used to determine parameters for the specific points to be converted. + +Since Depth coordinates are a projective, the apparent size of objects in depth coordinates (measured in pixels) +will increase as an object moves closer to the sensor. The size of objects in the World coordinate system is independent of +distance from the sensor. + +Note that converting from Depth to World coordinates is relatively expensive computationally. It is generally not practical to convert +the entire raw depth map to World coordinates. A better approach is to have your computer vision algorithm work in Depth +coordinates for as long as possible, and only converting a few specific points to World coordinates right before output. + +Note that when converting from Depth to World or vice versa, the Z value remains the same. +*/ +class CoordinateConverter +{ +public: + /** + Converts a single point from the World coordinate system to the Depth coordinate system. + @param [in] depthStream Reference to an openni::VideoStream that will be used to determine the format of the Depth coordinates + @param [in] worldX The X coordinate of the point to be converted, measured in millimeters in World coordinates + @param [in] worldY The Y coordinate of the point to be converted, measured in millimeters in World coordinates + @param [in] worldZ The Z coordinate of the point to be converted, measured in millimeters in World coordinates + @param [out] pDepthX Pointer to a place to store the X coordinate of the output value, measured in pixels with 0 at far left of image + @param [out] pDepthY Pointer to a place to store the Y coordinate of the output value, measured in pixels with 0 at top of image + @param [out] pDepthZ Pointer to a place to store the Z(depth) coordinate of the output value, measured in the @ref PixelFormat of depthStream + */ + static Status convertWorldToDepth(const VideoStream& depthStream, float worldX, float worldY, float worldZ, int* pDepthX, int* pDepthY, DepthPixel* pDepthZ) + { + float depthX, depthY, depthZ; + Status rc = (Status)oniCoordinateConverterWorldToDepth(depthStream._getHandle(), worldX, worldY, worldZ, &depthX, &depthY, &depthZ); + *pDepthX = (int)depthX; + *pDepthY = (int)depthY; + *pDepthZ = (DepthPixel)depthZ; + return rc; + } + + /** + Converts a single point from the World coordinate system to a floating point representation of the Depth coordinate system + @param [in] depthStream Reference to an openni::VideoStream that will be used to determine the format of the Depth coordinates + @param [in] worldX The X coordinate of the point to be converted, measured in millimeters in World coordinates + @param [in] worldY The Y coordinate of the point to be converted, measured in millimeters in World coordinates + @param [in] worldZ The Z coordinate of the point to be converted, measured in millimeters in World coordinates + @param [out] pDepthX Pointer to a place to store the X coordinate of the output value, measured in pixels with 0.0 at far left of the image + @param [out] pDepthY Pointer to a place to store the Y coordinate of the output value, measured in pixels with 0.0 at the top of the image + @param [out] pDepthZ Pointer to a place to store the Z(depth) coordinate of the output value, measured in millimeters with 0.0 at the camera lens + */ + static Status convertWorldToDepth(const VideoStream& depthStream, float worldX, float worldY, float worldZ, float* pDepthX, float* pDepthY, float* pDepthZ) + { + return (Status)oniCoordinateConverterWorldToDepth(depthStream._getHandle(), worldX, worldY, worldZ, pDepthX, pDepthY, pDepthZ); + } + + /** + Converts a single point from the Depth coordinate system to the World coordinate system. + @param [in] depthStream Reference to an openi::VideoStream that will be used to determine the format of the Depth coordinates + @param [in] depthX The X coordinate of the point to be converted, measured in pixels with 0 at the far left of the image + @param [in] depthY The Y coordinate of the point to be converted, measured in pixels with 0 at the top of the image + @param [in] depthZ the Z(depth) coordinate of the point to be converted, measured in the @ref PixelFormat of depthStream + @param [out] pWorldX Pointer to a place to store the X coordinate of the output value, measured in millimeters in World coordinates + @param [out] pWorldY Pointer to a place to store the Y coordinate of the output value, measured in millimeters in World coordinates + @param [out] pWorldZ Pointer to a place to store the Z coordinate of the output value, measured in millimeters in World coordinates + */ + static Status convertDepthToWorld(const VideoStream& depthStream, int depthX, int depthY, DepthPixel depthZ, float* pWorldX, float* pWorldY, float* pWorldZ) + { + return (Status)oniCoordinateConverterDepthToWorld(depthStream._getHandle(), float(depthX), float(depthY), float(depthZ), pWorldX, pWorldY, pWorldZ); + } + + /** + Converts a single point from a floating point representation of the Depth coordinate system to the World coordinate system. + @param [in] depthStream Reference to an openi::VideoStream that will be used to determine the format of the Depth coordinates + @param [in] depthX The X coordinate of the point to be converted, measured in pixels with 0.0 at the far left of the image + @param [in] depthY The Y coordinate of the point to be converted, measured in pixels with 0.0 at the top of the image + @param [in] depthZ Z(depth) coordinate of the point to be converted, measured in the @ref PixelFormat of depthStream + @param [out] pWorldX Pointer to a place to store the X coordinate of the output value, measured in millimeters in World coordinates + @param [out] pWorldY Pointer to a place to store the Y coordinate of the output value, measured in millimeters in World coordinates + @param [out] pWorldZ Pointer to a place to store the Z coordinate of the output value, measured in millimeters in World coordinates + */ + static Status convertDepthToWorld(const VideoStream& depthStream, float depthX, float depthY, float depthZ, float* pWorldX, float* pWorldY, float* pWorldZ) + { + return (Status)oniCoordinateConverterDepthToWorld(depthStream._getHandle(), depthX, depthY, depthZ, pWorldX, pWorldY, pWorldZ); + } + + /** + For a given depth point, provides the coordinates of the corresponding color value. Useful for superimposing the depth and color images. + This operation is the same as turning on registration, but is performed on a single pixel rather than the whole image. + @param [in] depthStream Reference to a openni::VideoStream that produced the depth value + @param [in] colorStream Reference to a openni::VideoStream that we want to find the appropriate color pixel in + @param [in] depthX X value of the depth point, given in Depth coordinates and measured in pixels + @param [in] depthY Y value of the depth point, given in Depth coordinates and measured in pixels + @param [in] depthZ Z(depth) value of the depth point, given in the @ref PixelFormat of depthStream + @param [out] pColorX The X coordinate of the color pixel that overlaps the given depth pixel, measured in pixels + @param [out] pColorY The Y coordinate of the color pixel that overlaps the given depth pixel, measured in pixels + */ + static Status convertDepthToColor(const VideoStream& depthStream, const VideoStream& colorStream, int depthX, int depthY, DepthPixel depthZ, int* pColorX, int* pColorY) + { + return (Status)oniCoordinateConverterDepthToColor(depthStream._getHandle(), colorStream._getHandle(), depthX, depthY, depthZ, pColorX, pColorY); + } +}; + +/** + * The Recorder class is used to record streams to an ONI file. + * + * After a recorder is instantiated, it must be initialized with a specific filename where + * the recording will be stored. The recorder is then attached to one or more streams. Once + * this is complete, the recorder can be told to start recording. The recorder will store + * every frame from every stream to the specified file. Later, this file can be used to + * initialize a file Device, and used to play back the same data that was recorded. + * + * Opening a file device is done by passing its path as the uri to the @ref Device::open() method. + * + * @see PlaybackControl for options available to play a reorded file. + * + */ +class Recorder +{ +public: + /** + * Creates a recorder. The recorder is not valid, i.e. @ref isValid() returns + * false. You must initialize the recorder before use with @ref create(). + */ + Recorder() : m_recorder(NULL) + { + } + + /** + * Destroys a recorder. This will also stop recording. + */ + ~Recorder() + { + destroy(); + } + + /** + * Initializes a recorder. You can initialize the recorder only once. Attempts + * to intialize more than once will result in an error code being returned. + * + * Initialization assigns the recorder to an output file that will be used for + * recording. Before use, the @ref attach() function must also be used to assign input + * data to the Recorder. + * + * @param [in] fileName The name of a file which will contain the recording. + * @returns Status code which indicates success or failure of the operation. + */ + Status create(const char* fileName) + { + if (!isValid()) + { + return (Status)oniCreateRecorder(fileName, &m_recorder); + } + return STATUS_ERROR; + } + + /** + * Verifies if the recorder is valid, i.e. if one can record with this recorder. A + * recorder object is not valid until the @ref create() method is called. + * + * @returns true if the recorder has been intialized, false otherwise. + */ + bool isValid() const + { + return NULL != getHandle(); + } + + /** + * Attaches a stream to the recorder. Note, this won't start recording, you + * should explicitly start it using @ref start() method. As soon as the recording + * process has been started, no more streams can be attached to the recorder. + * + * @param [in] stream The stream to be recorded. + * @param [in] allowLossyCompression [Optional] If this value is true, the recorder might use + * a lossy compression, which means that when the recording will be played-back, there might + * be small differences from the original frame. Default value is false. + */ + Status attach(VideoStream& stream, bool allowLossyCompression = false) + { + if (!isValid() || !stream.isValid()) + { + return STATUS_ERROR; + } + return (Status)oniRecorderAttachStream( + m_recorder, + stream._getHandle(), + allowLossyCompression); + } + + /** + * Starts recording. + * Once this method is called, the recorder will take all subsequent frames from the attached streams + * and store them in the file. + * You may not attach additional streams once recording was started. + */ + Status start() + { + if (!isValid()) + { + return STATUS_ERROR; + } + return (Status)oniRecorderStart(m_recorder); + } + + /** + * Stops recording. You may use @ref start() to resume the recording. + */ + void stop() + { + if (isValid()) + { + oniRecorderStop(m_recorder); + } + } + + /** + Destroys the recorder object. + */ + void destroy() + { + if (isValid()) + { + oniRecorderDestroy(&m_recorder); + } + } + +private: + Recorder(const Recorder&); + Recorder& operator=(const Recorder&); + + /** + * Returns a handle of this recorder. + */ + OniRecorderHandle getHandle() const + { + return m_recorder; + } + + + OniRecorderHandle m_recorder; +}; + +// Implemetation +Status VideoStream::create(const Device& device, SensorType sensorType) +{ + OniStreamHandle streamHandle; + Status rc = (Status)oniDeviceCreateStream(device._getHandle(), (OniSensorType)sensorType, &streamHandle); + if (rc != STATUS_OK) + { + return rc; + } + + m_isOwner = true; + _setHandle(streamHandle); + + if (isPropertySupported(STREAM_PROPERTY_AUTO_WHITE_BALANCE) && isPropertySupported(STREAM_PROPERTY_AUTO_EXPOSURE)) + { + m_pCameraSettings = new CameraSettings(this); + } + + return STATUS_OK; +} + +void VideoStream::destroy() +{ + if (!isValid()) + { + return; + } + + if (m_pCameraSettings != NULL) + { + delete m_pCameraSettings; + m_pCameraSettings = NULL; + } + + if (m_stream != NULL) + { + if(m_isOwner) + oniStreamDestroy(m_stream); + m_stream = NULL; + } +} + +Status Device::open(const char* uri) +{ + //If we are not the owners, we stick with our own device + if(!m_isOwner) + { + if(isValid()){ + return STATUS_OK; + }else{ + return STATUS_OUT_OF_FLOW; + } + } + + OniDeviceHandle deviceHandle; + Status rc = (Status)oniDeviceOpen(uri, &deviceHandle); + if (rc != STATUS_OK) + { + return rc; + } + + _setHandle(deviceHandle); + + return STATUS_OK; +} + +Status Device::_openEx(const char* uri, const char* mode) +{ + //If we are not the owners, we stick with our own device + if(!m_isOwner) + { + if(isValid()){ + return STATUS_OK; + }else{ + return STATUS_OUT_OF_FLOW; + } + } + + OniDeviceHandle deviceHandle; + Status rc = (Status)oniDeviceOpenEx(uri, mode, &deviceHandle); + if (rc != STATUS_OK) + { + return rc; + } + + _setHandle(deviceHandle); + + return STATUS_OK; +} + +Status Device::_setHandle(OniDeviceHandle deviceHandle) +{ + if (m_device == NULL) + { + m_device = deviceHandle; + + clearSensors(); + + oniDeviceGetInfo(m_device, &m_deviceInfo); + + if (isFile()) + { + m_pPlaybackControl = new PlaybackControl(this); + } + + // Read deviceInfo + return STATUS_OK; + } + + return STATUS_OUT_OF_FLOW; +} + +void Device::close() +{ + if (m_pPlaybackControl != NULL) + { + delete m_pPlaybackControl; + m_pPlaybackControl = NULL; + } + + if (m_device != NULL) + { + if(m_isOwner) + { + oniDeviceClose(m_device); + } + + m_device = NULL; + } +} + + +} + +#endif // _OPEN_NI_HPP_ diff --git a/Include/PS1080.h b/Include/PS1080.h new file mode 100644 index 0000000..561f766 --- /dev/null +++ b/Include/PS1080.h @@ -0,0 +1,632 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _PS1080_H_ +#define _PS1080_H_ + +#include + +/** The maximum permitted Xiron device name string length. */ +#define XN_DEVICE_MAX_STRING_LENGTH 200 + +/* + * private properties of PS1080 devices. + * + * @remarks + * properties structure is 0x1080XXYY where XX is range and YY is code. + * range values: + * F0 - device properties + * E0 - device commands + * 00 - common stream properties + * 10 - depth stream properties + * 20 - color stream properties + */ +enum +{ + /*******************************************************************/ + /* Device properties */ + /*******************************************************************/ + + /** unsigned long long (XnSensorUsbInterface) */ + XN_MODULE_PROPERTY_USB_INTERFACE = 0x1080F001, // "UsbInterface" + /** Boolean */ + XN_MODULE_PROPERTY_MIRROR = 0x1080F002, // "Mirror" + /** unsigned long long, get only */ + XN_MODULE_PROPERTY_RESET_SENSOR_ON_STARTUP = 0x1080F004, // "ResetSensorOnStartup" + /** unsigned long long, get only */ + XN_MODULE_PROPERTY_LEAN_INIT = 0x1080F005, // "LeanInit" + /** char[XN_DEVICE_MAX_STRING_LENGTH], get only */ + XN_MODULE_PROPERTY_SERIAL_NUMBER = 0x1080F006, // "ID" + /** XnVersions, get only */ + XN_MODULE_PROPERTY_VERSION = 0x1080F007, // "Version" + /** Boolean */ + XN_MODULE_PROPERTY_FIRMWARE_FRAME_SYNC = 0x1080F008, + /** Boolean */ + XN_MODULE_PROPERTY_HOST_TIMESTAMPS = 0x1080FF77, // "HostTimestamps" + /** Boolean */ + XN_MODULE_PROPERTY_CLOSE_STREAMS_ON_SHUTDOWN = 0x1080FF78, // "CloseStreamsOnShutdown" + /** Integer */ + XN_MODULE_PROPERTY_FIRMWARE_LOG_INTERVAL = 0x1080FF7F, // "FirmwareLogInterval" + /** Boolean */ + XN_MODULE_PROPERTY_PRINT_FIRMWARE_LOG = 0x1080FF80, // "FirmwareLogPrint" + /** Integer */ + XN_MODULE_PROPERTY_FIRMWARE_LOG_FILTER = 0x1080FF81, // "FirmwareLogFilter" + /** String, get only */ + XN_MODULE_PROPERTY_FIRMWARE_LOG = 0x1080FF82, // "FirmwareLog" + /** Integer */ + XN_MODULE_PROPERTY_FIRMWARE_CPU_INTERVAL = 0x1080FF83, // "FirmwareCPUInterval" + /** String, get only */ + XN_MODULE_PROPERTY_PHYSICAL_DEVICE_NAME = 0x1080FF7A, // "PhysicalDeviceName" + /** String, get only */ + XN_MODULE_PROPERTY_VENDOR_SPECIFIC_DATA = 0x1080FF7B, // "VendorSpecificData" + /** String, get only */ + XN_MODULE_PROPERTY_SENSOR_PLATFORM_STRING = 0x1080FF7C, // "SensorPlatformString" + + /*******************************************************************/ + /* Device commands (activated via SetProperty/GetProperty) */ + /*******************************************************************/ + + /** XnInnerParam */ + XN_MODULE_PROPERTY_FIRMWARE_PARAM = 0x1080E001, // "FirmwareParam" + /** unsigned long long, set only */ + XN_MODULE_PROPERTY_RESET = 0x1080E002, // "Reset" + /** XnControlProcessingData */ + XN_MODULE_PROPERTY_IMAGE_CONTROL = 0x1080E003, // "ImageControl" + /** XnControlProcessingData */ + XN_MODULE_PROPERTY_DEPTH_CONTROL = 0x1080E004, // "DepthControl" + /** XnAHBData */ + XN_MODULE_PROPERTY_AHB = 0x1080E005, // "AHB" + /** XnLedState */ + XN_MODULE_PROPERTY_LED_STATE = 0x1080E006, // "LedState" + /** Boolean */ + XN_MODULE_PROPERTY_EMITTER_STATE = 0x1080E007, // "EmitterState" + + /** XnCmosBlankingUnits */ + XN_MODULE_PROPERTY_CMOS_BLANKING_UNITS = 0x1080FF74, // "CmosBlankingUnits" + /** XnCmosBlankingTime */ + XN_MODULE_PROPERTY_CMOS_BLANKING_TIME = 0x1080FF75, // "CmosBlankingTime" + /** XnFlashFileList, get only */ + XN_MODULE_PROPERTY_FILE_LIST = 0x1080FF84, // "FileList" + /** XnParamFlashData, get only */ + XN_MODULE_PROPERTY_FLASH_CHUNK = 0x1080FF85, // "FlashChunk" + XN_MODULE_PROPERTY_FILE = 0x1080FF86, // "FlashFile" + /** Integer */ + XN_MODULE_PROPERTY_DELETE_FILE = 0x1080FF87, // "DeleteFile" + XN_MODULE_PROPERTY_FILE_ATTRIBUTES = 0x1080FF88, // "FileAttributes" + XN_MODULE_PROPERTY_TEC_SET_POINT = 0x1080FF89, // "TecSetPoint" + /** get only */ + XN_MODULE_PROPERTY_TEC_STATUS = 0x1080FF8A, // "TecStatus" + /** get only */ + XN_MODULE_PROPERTY_TEC_FAST_CONVERGENCE_STATUS = 0x1080FF8B, // "TecFastConvergenceStatus" + XN_MODULE_PROPERTY_EMITTER_SET_POINT = 0x1080FF8C, // "EmitterSetPoint" + /** get only */ + XN_MODULE_PROPERTY_EMITTER_STATUS = 0x1080FF8D, // "EmitterStatus" + XN_MODULE_PROPERTY_I2C = 0x1080FF8E, // "I2C" + /** Integer, set only */ + XN_MODULE_PROPERTY_BIST = 0x1080FF8F, // "BIST" + /** XnProjectorFaultData, set only */ + XN_MODULE_PROPERTY_PROJECTOR_FAULT = 0x1080FF90, // "ProjectorFault" + /** Boolean, set only */ + XN_MODULE_PROPERTY_APC_ENABLED = 0x1080FF91, // "APCEnabled" + /** Boolean */ + XN_MODULE_PROPERTY_FIRMWARE_TEC_DEBUG_PRINT = 0x1080FF92, // "TecDebugPrint" + + /*******************************************************************/ + /* Common stream properties */ + /*******************************************************************/ + + /** unsigned long long */ + XN_STREAM_PROPERTY_INPUT_FORMAT = 0x10800001, // "InputFormat" + /** unsigned long long (XnCroppingMode) */ + XN_STREAM_PROPERTY_CROPPING_MODE = 0x10800002, // "CroppingMode" + + /*******************************************************************/ + /* Depth stream properties */ + /*******************************************************************/ + + /** unsigned long long */ + XN_STREAM_PROPERTY_CLOSE_RANGE = 0x1080F003, // "CloseRange" + /** XnPixelRegistration - get only */ + XN_STREAM_PROPERTY_PIXEL_REGISTRATION = 0x10801001, // "PixelRegistration" + /** unsigned long long */ + XN_STREAM_PROPERTY_WHITE_BALANCE_ENABLED = 0x10801002, // "WhiteBalancedEnabled" + /** unsigned long long */ + XN_STREAM_PROPERTY_GAIN = 0x10801003, // "Gain" + /** unsigned long long */ + XN_STREAM_PROPERTY_HOLE_FILTER = 0x10801004, // "HoleFilter" + /** unsigned long long (XnProcessingType) */ + XN_STREAM_PROPERTY_REGISTRATION_TYPE = 0x10801005, // "RegistrationType" + /** XnDepthAGCBin* */ + XN_STREAM_PROPERTY_AGC_BIN = 0x10801006, // "AGCBin" + /** unsigned long long, get only */ + XN_STREAM_PROPERTY_CONST_SHIFT = 0x10801007, // "ConstShift" + /** unsigned long long, get only */ + XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR = 0x10801008, // "PixelSizeFactor" + /** unsigned long long, get only */ + XN_STREAM_PROPERTY_MAX_SHIFT = 0x10801009, // "MaxShift" + /** unsigned long long, get only */ + XN_STREAM_PROPERTY_PARAM_COEFF = 0x1080100A, // "ParamCoeff" + /** unsigned long long, get only */ + XN_STREAM_PROPERTY_SHIFT_SCALE = 0x1080100B, // "ShiftScale" + /** unsigned long long, get only */ + XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE = 0x1080100C, // "ZPD" + /** double, get only */ + XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE = 0x1080100D, // "ZPPS" + /** double, get only */ + XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE = 0x1080100E, // "LDDIS" + /** double, get only */ + XN_STREAM_PROPERTY_DCMOS_RCMOS_DISTANCE = 0x1080100F, // "DCRCDIS" + /** OniDepthPixel[], get only */ + XN_STREAM_PROPERTY_S2D_TABLE = 0x10801010, // "S2D" + /** unsigned short[], get only */ + XN_STREAM_PROPERTY_D2S_TABLE = 0x10801011, // "D2S" + /** get only */ + XN_STREAM_PROPERTY_DEPTH_SENSOR_CALIBRATION_INFO = 0x10801012, + /** Boolean */ + XN_STREAM_PROPERTY_GMC_MODE = 0x1080FF44, // "GmcMode" + /** Boolean */ + XN_STREAM_PROPERTY_GMC_DEBUG = 0x1080FF45, // "GmcDebug" + /** Boolean */ + XN_STREAM_PROPERTY_WAVELENGTH_CORRECTION = 0x1080FF46, // "WavelengthCorrection" + /** Boolean */ + XN_STREAM_PROPERTY_WAVELENGTH_CORRECTION_DEBUG = 0x1080FF47, // "WavelengthCorrectionDebug" + + /*******************************************************************/ + /* Color stream properties */ + /*******************************************************************/ + /** Integer */ + XN_STREAM_PROPERTY_FLICKER = 0x10802001, // "Flicker" +}; + +typedef enum +{ + XN_SENSOR_FW_VER_UNKNOWN = 0, + XN_SENSOR_FW_VER_0_17 = 1, + XN_SENSOR_FW_VER_1_1 = 2, + XN_SENSOR_FW_VER_1_2 = 3, + XN_SENSOR_FW_VER_3_0 = 4, + XN_SENSOR_FW_VER_4_0 = 5, + XN_SENSOR_FW_VER_5_0 = 6, + XN_SENSOR_FW_VER_5_1 = 7, + XN_SENSOR_FW_VER_5_2 = 8, + XN_SENSOR_FW_VER_5_3 = 9, + XN_SENSOR_FW_VER_5_4 = 10, + XN_SENSOR_FW_VER_5_5 = 11, + XN_SENSOR_FW_VER_5_6 = 12, + XN_SENSOR_FW_VER_5_7 = 13, + XN_SENSOR_FW_VER_5_8 = 14, +} XnFWVer; + +typedef enum { + XN_SENSOR_VER_UNKNOWN = 0, + XN_SENSOR_VER_2_0 = 1, + XN_SENSOR_VER_3_0 = 2, + XN_SENSOR_VER_4_0 = 3, + XN_SENSOR_VER_5_0 = 4 +} XnSensorVer; + +typedef enum { + XN_SENSOR_HW_VER_UNKNOWN = 0, + XN_SENSOR_HW_VER_FPDB_10 = 1, + XN_SENSOR_HW_VER_CDB_10 = 2, + XN_SENSOR_HW_VER_RD_3 = 3, + XN_SENSOR_HW_VER_RD_5 = 4, + XN_SENSOR_HW_VER_RD1081 = 5, + XN_SENSOR_HW_VER_RD1082 = 6, + XN_SENSOR_HW_VER_RD109 = 7 +} XnHWVer; + +typedef enum { + XN_SENSOR_CHIP_VER_UNKNOWN = 0, + XN_SENSOR_CHIP_VER_PS1000 = 1, + XN_SENSOR_CHIP_VER_PS1080 = 2, + XN_SENSOR_CHIP_VER_PS1080A6 = 3 +} XnChipVer; + +typedef enum +{ + XN_CMOS_TYPE_IMAGE = 0, + XN_CMOS_TYPE_DEPTH = 1, + + XN_CMOS_COUNT +} XnCMOSType; + +typedef enum +{ + XN_IO_IMAGE_FORMAT_BAYER = 0, + XN_IO_IMAGE_FORMAT_YUV422 = 1, + XN_IO_IMAGE_FORMAT_JPEG = 2, + XN_IO_IMAGE_FORMAT_JPEG_420 = 3, + XN_IO_IMAGE_FORMAT_JPEG_MONO = 4, + XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422 = 5, + XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER = 6, + XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV = 7, +} XnIOImageFormats; + +typedef enum +{ + XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT = 0, + XN_IO_DEPTH_FORMAT_COMPRESSED_PS = 1, + XN_IO_DEPTH_FORMAT_UNCOMPRESSED_10_BIT = 2, + XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT = 3, + XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT = 4, +} XnIODepthFormats; + +typedef enum +{ + XN_RESET_TYPE_POWER = 0, + XN_RESET_TYPE_SOFT = 1, + XN_RESET_TYPE_SOFT_FIRST = 2, +} XnParamResetType; + +typedef enum XnSensorUsbInterface +{ + XN_SENSOR_USB_INTERFACE_DEFAULT = 0, + XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS = 1, + XN_SENSOR_USB_INTERFACE_BULK_ENDPOINTS = 2, + XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS_LOW_DEPTH = 3, +} XnSensorUsbInterface; + +typedef enum XnProcessingType +{ + XN_PROCESSING_DONT_CARE = 0, + XN_PROCESSING_HARDWARE = 1, + XN_PROCESSING_SOFTWARE = 2, +} XnProcessingType; + +typedef enum XnCroppingMode +{ + XN_CROPPING_MODE_NORMAL = 1, + XN_CROPPING_MODE_INCREASED_FPS = 2, + XN_CROPPING_MODE_SOFTWARE_ONLY = 3, +} XnCroppingMode; + +enum +{ + XN_ERROR_STATE_OK = 0, + XN_ERROR_STATE_DEVICE_PROJECTOR_FAULT = 1, + XN_ERROR_STATE_DEVICE_OVERHEAT = 2, +}; + +typedef enum XnFirmwareCroppingMode +{ + XN_FIRMWARE_CROPPING_MODE_DISABLED = 0, + XN_FIRMWARE_CROPPING_MODE_NORMAL = 1, + XN_FIRMWARE_CROPPING_MODE_INCREASED_FPS = 2, +} XnFirmwareCroppingMode; + +typedef enum +{ + XnLogFilterDebug = 0x0001, + XnLogFilterInfo = 0x0002, + XnLogFilterError = 0x0004, + XnLogFilterProtocol = 0x0008, + XnLogFilterAssert = 0x0010, + XnLogFilterConfig = 0x0020, + XnLogFilterFrameSync = 0x0040, + XnLogFilterAGC = 0x0080, + XnLogFilterTelems = 0x0100, + + XnLogFilterAll = 0xFFFF +} XnLogFilter; + +typedef enum +{ + XnFileAttributeReadOnly = 0x8000 +} XnFilePossibleAttributes; + +typedef enum +{ + XnFlashFileTypeFileTable = 0x00, + XnFlashFileTypeScratchFile = 0x01, + XnFlashFileTypeBootSector = 0x02, + XnFlashFileTypeBootManager = 0x03, + XnFlashFileTypeCodeDownloader = 0x04, + XnFlashFileTypeMonitor = 0x05, + XnFlashFileTypeApplication = 0x06, + XnFlashFileTypeFixedParams = 0x07, + XnFlashFileTypeDescriptors = 0x08, + XnFlashFileTypeDefaultParams = 0x09, + XnFlashFileTypeImageCmos = 0x0A, + XnFlashFileTypeDepthCmos = 0x0B, + XnFlashFileTypeAlgorithmParams = 0x0C, + XnFlashFileTypeReferenceQVGA = 0x0D, + XnFlashFileTypeReferenceVGA = 0x0E, + XnFlashFileTypeMaintenance = 0x0F, + XnFlashFileTypeDebugParams = 0x10, + XnFlashFileTypePrimeProcessor = 0x11, + XnFlashFileTypeGainControl = 0x12, + XnFlashFileTypeRegistartionParams = 0x13, + XnFlashFileTypeIDParams = 0x14, + XnFlashFileTypeSensorTECParams = 0x15, + XnFlashFileTypeSensorAPCParams = 0x16, + XnFlashFileTypeSensorProjectorFaultParams = 0x17, + XnFlashFileTypeProductionFile = 0x18, + XnFlashFileTypeUpgradeInProgress = 0x19, + XnFlashFileTypeWavelengthCorrection = 0x1A, + XnFlashFileTypeGMCReferenceOffset = 0x1B, + XnFlashFileTypeSensorNESAParams = 0x1C, + XnFlashFileTypeSensorFault = 0x1D, + XnFlashFileTypeVendorData = 0x1E, +} XnFlashFileType; + +typedef enum XnBistType +{ + //Auto tests + XN_BIST_IMAGE_CMOS = 1 << 0, + XN_BIST_IR_CMOS = 1 << 1, + XN_BIST_POTENTIOMETER = 1 << 2, + XN_BIST_FLASH = 1 << 3, + XN_BIST_FULL_FLASH = 1 << 4, + XN_BIST_PROJECTOR_TEST_MASK = 1 << 5, + XN_BIST_TEC_TEST_MASK = 1 << 6, + + // Manual tests + XN_BIST_NESA_TEST_MASK = 1 << 7, + XN_BIST_NESA_UNLIMITED_TEST_MASK = 1 << 8, + + // Mask of all the auto tests + XN_BIST_ALL = (0xFFFFFFFF & ~XN_BIST_NESA_TEST_MASK & ~XN_BIST_NESA_UNLIMITED_TEST_MASK), + +} XnBistType; + +typedef enum XnBistError +{ + XN_BIST_RAM_TEST_FAILURE = 1 << 0, + XN_BIST_IR_CMOS_CONTROL_BUS_FAILURE = 1 << 1, + XN_BIST_IR_CMOS_DATA_BUS_FAILURE = 1 << 2, + XN_BIST_IR_CMOS_BAD_VERSION = 1 << 3, + XN_BIST_IR_CMOS_RESET_FAILUE = 1 << 4, + XN_BIST_IR_CMOS_TRIGGER_FAILURE = 1 << 5, + XN_BIST_IR_CMOS_STROBE_FAILURE = 1 << 6, + XN_BIST_COLOR_CMOS_CONTROL_BUS_FAILURE = 1 << 7, + XN_BIST_COLOR_CMOS_DATA_BUS_FAILURE = 1 << 8, + XN_BIST_COLOR_CMOS_BAD_VERSION = 1 << 9, + XN_BIST_COLOR_CMOS_RESET_FAILUE = 1 << 10, + XN_BIST_FLASH_WRITE_LINE_FAILURE = 1 << 11, + XN_BIST_FLASH_TEST_FAILURE = 1 << 12, + XN_BIST_POTENTIOMETER_CONTROL_BUS_FAILURE = 1 << 13, + XN_BIST_POTENTIOMETER_FAILURE = 1 << 14, + XN_BIST_AUDIO_TEST_FAILURE = 1 << 15, + XN_BIST_PROJECTOR_TEST_LD_FAIL = 1 << 16, + XN_BIST_PROJECTOR_TEST_LD_FAILSAFE_TRIG_FAIL = 1 << 17, + XN_BIST_PROJECTOR_TEST_FAILSAFE_HIGH_FAIL = 1 << 18, + XN_BIST_PROJECTOR_TEST_FAILSAFE_LOW_FAIL = 1 << 19, + XN_TEC_TEST_HEATER_CROSSED = 1 << 20, + XN_TEC_TEST_HEATER_DISCONNETED = 1 << 21, + XN_TEC_TEST_TEC_CROSSED = 1 << 22, + XN_TEC_TEST_TEC_FAULT = 1 << 23, +} XnBistError; + +typedef enum XnDepthCMOSType +{ + XN_DEPTH_CMOS_NONE = 0, + XN_DEPTH_CMOS_MT9M001 = 1, + XN_DEPTH_CMOS_AR130 = 2, +} XnDepthCMOSType; + +typedef enum XnImageCMOSType +{ + XN_IMAGE_CMOS_NONE = 0, + XN_IMAGE_CMOS_MT9M112 = 1, + XN_IMAGE_CMOS_MT9D131 = 2, + XN_IMAGE_CMOS_MT9M114 = 3, +} XnImageCMOSType; + +#define XN_IO_MAX_I2C_BUFFER_SIZE 10 +#define XN_MAX_LOG_SIZE (6*1024) + +#pragma pack (push, 1) + +typedef struct XnSDKVersion +{ + unsigned char nMajor; + unsigned char nMinor; + unsigned char nMaintenance; + unsigned short nBuild; +} XnSDKVersion; + +typedef struct { + unsigned char nMajor; + unsigned char nMinor; + unsigned short nBuild; + unsigned int nChip; + unsigned short nFPGA; + unsigned short nSystemVersion; + + XnSDKVersion SDK; + + XnHWVer HWVer; + XnFWVer FWVer; + XnSensorVer SensorVer; + XnChipVer ChipVer; +} XnVersions; + +typedef struct +{ + unsigned short nParam; + unsigned short nValue; +} XnInnerParamData; + +typedef struct XnDepthAGCBin +{ + unsigned short nBin; + unsigned short nMin; + unsigned short nMax; +} XnDepthAGCBin; + +typedef struct XnControlProcessingData +{ + unsigned short nRegister; + unsigned short nValue; +} XnControlProcessingData; + +typedef struct XnAHBData +{ + unsigned int nRegister; + unsigned int nValue; + unsigned int nMask; +} XnAHBData; + +typedef struct XnPixelRegistration +{ + unsigned int nDepthX; + unsigned int nDepthY; + uint16_t nDepthValue; + unsigned int nImageXRes; + unsigned int nImageYRes; + unsigned int nImageX; // out + unsigned int nImageY; // out +} XnPixelRegistration; + +typedef struct XnLedState +{ + uint16_t nLedID; + uint16_t nState; +} XnLedState; + +typedef struct XnCmosBlankingTime +{ + XnCMOSType nCmosID; + float nTimeInMilliseconds; + uint16_t nNumberOfFrames; +} XnCmosBlankingTime; + +typedef struct XnCmosBlankingUnits +{ + XnCMOSType nCmosID; + uint16_t nUnits; + uint16_t nNumberOfFrames; +} XnCmosBlankingUnits; + +typedef struct XnI2CWriteData +{ + uint16_t nBus; + uint16_t nSlaveAddress; + uint16_t cpWriteBuffer[XN_IO_MAX_I2C_BUFFER_SIZE]; + uint16_t nWriteSize; +} XnI2CWriteData; + +typedef struct XnI2CReadData +{ + uint16_t nBus; + uint16_t nSlaveAddress; + uint16_t cpReadBuffer[XN_IO_MAX_I2C_BUFFER_SIZE]; + uint16_t cpWriteBuffer[XN_IO_MAX_I2C_BUFFER_SIZE]; + uint16_t nReadSize; + uint16_t nWriteSize; +} XnI2CReadData; + +typedef struct XnTecData +{ + uint16_t m_SetPointVoltage; + uint16_t m_CompensationVoltage; + uint16_t m_TecDutyCycle; //duty cycle on heater/cooler + uint16_t m_HeatMode; //TRUE - heat, FALSE - cool + int32_t m_ProportionalError; + int32_t m_IntegralError; + int32_t m_DerivativeError; + uint16_t m_ScanMode; //0 - crude, 1 - precise +} XnTecData; + +typedef struct XnTecFastConvergenceData +{ + int16_t m_SetPointTemperature; // set point temperature in celsius, + // scaled by factor of 100 (extra precision) + int16_t m_MeasuredTemperature; // measured temperature in celsius, + // scaled by factor of 100 (extra precision) + int32_t m_ProportionalError; // proportional error in system clocks + int32_t m_IntegralError; // integral error in system clocks + int32_t m_DerivativeError; // derivative error in system clocks + uint16_t m_ScanMode; // 0 - initial, 1 - crude, 2 - precise + uint16_t m_HeatMode; // 0 - idle, 1 - heat, 2 - cool + uint16_t m_TecDutyCycle; // duty cycle on heater/cooler in percents + uint16_t m_TemperatureRange; // 0 - cool, 1 - room, 2 - warm +} XnTecFastConvergenceData; + +typedef struct XnEmitterData +{ + uint16_t m_State; //idle, calibrating + uint16_t m_SetPointVoltage; //this is what should be written to the XML + uint16_t m_SetPointClocks; //target cross duty cycle + uint16_t m_PD_Reading; //current cross duty cycle in system clocks(high time) + uint16_t m_EmitterSet; //duty cycle on emitter set in system clocks (high time). + uint16_t m_EmitterSettingLogic; //TRUE = positive logic, FALSE = negative logic + uint16_t m_LightMeasureLogic; //TRUE - positive logic, FALSE - negative logic + uint16_t m_IsAPCEnabled; + uint16_t m_EmitterSetStepSize; // in MilliVolts + uint16_t m_ApcTolerance; // in system clocks (only valid up till v5.2) + uint16_t m_SubClocking; //in system clocks (only valid from v5.3) + uint16_t m_Precision; // (only valid from v5.3) +} XnEmitterData; + +typedef struct +{ + uint16_t nId; + uint16_t nAttribs; +} XnFileAttributes; + +typedef struct +{ + uint32_t nOffset; + const char* strFileName; + uint16_t nAttributes; +} XnParamFileData; + +typedef struct +{ + uint32_t nOffset; + uint32_t nSize; + unsigned char* pData; +} XnParamFlashData; + +typedef struct { + uint16_t nId; + uint16_t nType; + uint32_t nVersion; + uint32_t nOffset; + uint32_t nSize; + uint16_t nCrc; + uint16_t nAttributes; + uint16_t nReserve; +} XnFlashFile; + +typedef struct +{ + XnFlashFile* pFiles; + uint16_t nFiles; +} XnFlashFileList; + +typedef struct XnProjectorFaultData +{ + uint16_t nMinThreshold; + uint16_t nMaxThreshold; + int32_t bProjectorFaultEvent; +} XnProjectorFaultData; + +typedef struct XnBist +{ + uint32_t nTestsMask; + uint32_t nFailures; +} XnBist; + +#pragma pack (pop) + +#endif //_PS1080_H_ \ No newline at end of file diff --git a/Include/PSLink.h b/Include/PSLink.h new file mode 100644 index 0000000..dd4d899 --- /dev/null +++ b/Include/PSLink.h @@ -0,0 +1,199 @@ +#ifndef __XN_PRIME_CLIENT_PROPS_H__ +#define __XN_PRIME_CLIENT_PROPS_H__ + +#include + +enum +{ + /**** Device properties ****/ + + /* XnDetailedVersion, get only */ + LINK_PROP_FW_VERSION = 0x12000001, // "FWVersion" + /* Int, get only */ + LINK_PROP_VERSIONS_INFO_COUNT = 0x12000002, // "VersionsInfoCount" + /* General - array - XnComponentVersion * count elements, get only */ + LINK_PROP_VERSIONS_INFO = 0x12000003, // "VersionsInfo" + /* Int - 0 means off, 1 means on. */ + LINK_PROP_EMITTER_ACTIVE = 0x12000008, // "EmitterActive" + /* String. Set only */ + LINK_PROP_PRESET_FILE = 0x1200000a, // "PresetFile" + /* Get only */ + LINK_PROP_BOOT_STATUS = 0x1200000b, + + /**** Device commands ****/ + /* XnCommandGetFwStreams */ + LINK_COMMAND_GET_FW_STREAM_LIST = 0x1200F001, + /* XnCommandCreateStream */ + LINK_COMMAND_CREATE_FW_STREAM = 0x1200F002, + /* XnCommandDestroyStream */ + LINK_COMMAND_DESTROY_FW_STREAM = 0x1200F003, + /* XnCommandStartStream */ + LINK_COMMAND_START_FW_STREAM = 0x1200F004, + /* XnCommandStopStream */ + LINK_COMMAND_STOP_FW_STREAM = 0x1200F005, + /* XnCommandGetFwStreamVideoModeList */ + LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE_LIST = 0x1200F006, + /* XnCommandSetFwStreamVideoMode */ + LINK_COMMAND_SET_FW_STREAM_VIDEO_MODE = 0x1200F007, + /* XnCommandGetFwStreamVideoMode */ + LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE = 0x1200F008, + + /**** Stream properties ****/ + /* Int. 1 - Shifts 9.3, 2 - Grayscale16, 3 - YUV422, 4 - Bayer8 */ + LINK_PROP_PIXEL_FORMAT = 0x12001001, // "PixelFormat" + /* Int. 0 - None, 1 - 8z, 2 - 16z, 3 - 24z, 4 - 6-bit, 5 - 10-bit, 6 - 11-bit, 7 - 12-bit */ + LINK_PROP_COMPRESSION = 0x12001002, // "Compression" + + /**** Depth Stream properties ****/ + /* Real, get only */ + LINK_PROP_DEPTH_SCALE = 0x1200000b, // "DepthScale" + /* Int, get only */ + LINK_PROP_MAX_SHIFT = 0x12002001, // "MaxShift" + /* Int, get only */ + LINK_PROP_ZERO_PLANE_DISTANCE = 0x12002002, // "ZPD" + /* Int, get only */ + LINK_PROP_CONST_SHIFT = 0x12002003, // "ConstShift" + /* Int, get only */ + LINK_PROP_PARAM_COEFF = 0x12002004, // "ParamCoeff" + /* Int, get only */ + LINK_PROP_SHIFT_SCALE = 0x12002005, // "ShiftScale" + /* Real, get only */ + LINK_PROP_ZERO_PLANE_PIXEL_SIZE = 0x12002006, // "ZPPS" + /* Real, get only */ + LINK_PROP_ZERO_PLANE_OUTPUT_PIXEL_SIZE = 0x12002007, // "ZPOPS" + /* Real, get only */ + LINK_PROP_EMITTER_DEPTH_CMOS_DISTANCE = 0x12002008, // "LDDIS" + /* General - array - MaxShift * XnDepthPixel elements, get only */ + LINK_PROP_SHIFT_TO_DEPTH_TABLE = 0x12002009, // "S2D" + /* General - array - MaxDepth * uint16_t elements, get only */ + LINK_PROP_DEPTH_TO_SHIFT_TABLE = 0x1200200a, // "D2S" +}; + +typedef enum XnFileZone +{ + XN_ZONE_FACTORY = 0x0000, + XN_ZONE_UPDATE = 0x0001, +} XnFileZone; + +typedef enum XnBootErrorCode +{ + XN_BOOT_OK = 0x0000, + XN_BOOT_BAD_CRC = 0x0001, + XN_BOOT_UPLOAD_IN_PROGRESS = 0x0002, + XN_BOOT_FW_LOAD_FAILED = 0x0003, +} XnBootErrorCode; + +typedef enum XnFwStreamType +{ + XN_FW_STREAM_TYPE_COLOR = 0x0001, + XN_FW_STREAM_TYPE_IR = 0x0002, + XN_FW_STREAM_TYPE_SHIFTS = 0x0003, + XN_FW_STREAM_TYPE_AUDIO = 0x0004, + XN_FW_STREAM_TYPE_DY = 0x0005, + XN_FW_STREAM_TYPE_LOG = 0x0008, +} XnFwStreamType; + +typedef enum XnFwPixelFormat +{ + XN_FW_PIXEL_FORMAT_NONE = 0x0000, + XN_FW_PIXEL_FORMAT_SHIFTS_9_3 = 0x0001, + XN_FW_PIXEL_FORMAT_GRAYSCALE16 = 0x0002, + XN_FW_PIXEL_FORMAT_YUV422 = 0x0003, + XN_FW_PIXEL_FORMAT_BAYER8 = 0x0004, +} XnFwPixelFormat; + +typedef enum XnFwCompressionType +{ + XN_FW_COMPRESSION_NONE = 0x0000, + XN_FW_COMPRESSION_8Z = 0x0001, + XN_FW_COMPRESSION_16Z = 0x0002, + XN_FW_COMPRESSION_24Z = 0x0003, + XN_FW_COMPRESSION_6_BIT_PACKED = 0x0004, + XN_FW_COMPRESSION_10_BIT_PACKED = 0x0005, + XN_FW_COMPRESSION_11_BIT_PACKED = 0x0006, + XN_FW_COMPRESSION_12_BIT_PACKED = 0x0007, +} XnFwCompressionType; + +#pragma pack (push, 1) + +#define XN_MAX_VERSION_MODIFIER_LENGTH 16 +typedef struct XnDetailedVersion +{ + uint8_t m_nMajor; + uint8_t m_nMinor; + uint16_t m_nMaintenance; + uint32_t m_nBuild; + char m_strModifier[XN_MAX_VERSION_MODIFIER_LENGTH]; +} XnDetailedVersion; + +typedef struct XnBootStatus +{ + XnFileZone zone; + XnBootErrorCode errorCode; +} XnBootStatus; + +typedef struct XnFwStreamInfo +{ + XnFwStreamType type; + char creationInfo[80]; +} XnFwStreamInfo; + +typedef struct XnFwStreamVideoMode +{ + uint32_t m_nXRes; + uint32_t m_nYRes; + uint32_t m_nFPS; + XnFwPixelFormat m_nPixelFormat; + XnFwCompressionType m_nCompression; +} XnFwStreamVideoMode; + +typedef struct XnCommandGetFwStreamList +{ + uint32_t count; // in: number of allocated elements in streams array. out: number of written elements in the array + XnFwStreamInfo* streams; +} XnCommandGetFwStreamList; + +typedef struct XnCommandCreateStream +{ + XnFwStreamType type; + const char* creationInfo; + uint32_t id; // out +} XnCommandCreateStream; + +typedef struct XnCommandDestroyStream +{ + uint32_t id; +} XnCommandDestroyStream; + +typedef struct XnCommandStartStream +{ + uint32_t id; +} XnCommandStartStream; + +typedef struct XnCommandStopStream +{ + uint32_t id; +} XnCommandStopStream; + +typedef struct XnCommandGetFwStreamVideoModeList +{ + int streamId; + uint32_t count; // in: number of allocated elements in videoModes array. out: number of written elements in the array + XnFwStreamVideoMode* videoModes; +} XnCommandGetFwStreamVideoModeList; + +typedef struct XnCommandSetFwStreamVideoMode +{ + int streamId; + XnFwStreamVideoMode videoMode; +} XnCommandSetFwStreamVideoMode; + +typedef struct XnCommandGetFwStreamVideoMode +{ + int streamId; + XnFwStreamVideoMode videoMode; // out +} XnCommandGetFwStreamVideoMode; + +#pragma pack (pop) + +#endif //__XN_PRIME_CLIENT_PROPS_H__ diff --git a/Include/PrimeSense.h b/Include/PrimeSense.h new file mode 100644 index 0000000..1517bdd --- /dev/null +++ b/Include/PrimeSense.h @@ -0,0 +1,229 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _PRIME_SENSE_H_ +#define _PRIME_SENSE_H_ + +#include + +/** +* Additional properties for PrimeSense devices +* +* @remarks +* properties structure is 0x1D27XXYY where XX is range and YY is code. +* range values: +* 00 - common stream properties +* 10 - depth stream properties +* E0 - device commands +* F0 - device properties +*/ +enum +{ + // Stream Properties + PS_PROPERTY_DUMP_DATA = 0x1d270001, // boolean + + // Device Properties + PS_PROPERTY_USB_INTERFACE = 0x1d27F001, // values from XnUsbInterfaceType +}; + +/** +* Additional commands for PrimeSense devices +* +* @remarks +* Commands structure is 0x1D27XXYY where XX is range and YY is code. +* range values: +* E0 - device commands +*/ +enum +{ + // Device Commands - use via invoke() + PS_COMMAND_AHB_READ = 0x1d27E001, // XnCommandAHB + PS_COMMAND_AHB_WRITE = 0x1d27E002, // XnCommandAHB + PS_COMMAND_I2C_READ = 0x1d27E003, // XnCommandI2C + PS_COMMAND_I2C_WRITE = 0x1d27E004, // XnCommandI2C + PS_COMMAND_SOFT_RESET = 0x1d27E005, // no arguments + PS_COMMAND_POWER_RESET = 0x1d27E006, // no arguments + PS_COMMAND_BEGIN_FIRMWARE_UPDATE = 0x1d27E007, // no arguments + PS_COMMAND_END_FIRMWARE_UPDATE = 0x1d27E008, // no arguments + PS_COMMAND_UPLOAD_FILE = 0x1d27E009, // XnCommandUploadFile + PS_COMMAND_DOWNLOAD_FILE = 0x1d27E00A, // XnCommandDownloadFile + PS_COMMAND_GET_FILE_LIST = 0x1d27E00B, // an array of XnFileEntry + PS_COMMAND_FORMAT_ZONE = 0x1d27E00C, // XnCommandFormatZone + PS_COMMAND_DUMP_ENDPOINT = 0x1d27E00D, // XnCommandDumpEndpoint + PS_COMMAND_GET_I2C_DEVICE_LIST = 0x1d27E00E, // XnCommandGetI2CDevices + PS_COMMAND_GET_BIST_LIST = 0x1d27E00F, // XnCommandGetBistList + PS_COMMAND_EXECUTE_BIST = 0x1d27E010, // XnCommandExecuteBist + PS_COMMAND_USB_TEST = 0x1d27E011, // XnCommandUsbTest + PS_COMMAND_GET_LOG_MASK_LIST = 0x1d27E012, // XnCommandGetLogMaskList + PS_COMMAND_SET_LOG_MASK_STATE = 0x1d27E013, // XnCommandSetLogMaskState + PS_COMMAND_START_LOG = 0x1d27E014, // no arguments + PS_COMMAND_STOP_LOG = 0x1d27E015, // no arguments +}; + +typedef enum XnUsbInterfaceType +{ + PS_USB_INTERFACE_DONT_CARE = 0, + PS_USB_INTERFACE_ISO_ENDPOINTS = 1, + PS_USB_INTERFACE_BULK_ENDPOINTS = 2, +} XnUsbInterfaceType; + +#pragma pack (push, 1) + +// Data Types +typedef struct XnFwFileVersion +{ + uint8_t major; + uint8_t minor; + uint8_t maintenance; + uint8_t build; +} XnFwFileVersion; + +typedef enum XnFwFileFlags +{ + XN_FILE_FLAG_BAD_CRC = 0x0001, +} XnFwFileFlags; + +typedef struct XnFwFileEntry +{ + char name[32]; + XnFwFileVersion version; + uint32_t address; + uint32_t size; + uint16_t crc; + uint16_t zone; + XnFwFileFlags flags; // bitmap +} XnFwFileEntry; + +typedef struct XnI2CDeviceInfo +{ + uint32_t id; + char name[32]; +} XnI2CDeviceInfo; + +typedef struct XnBistInfo +{ + uint32_t id; + char name[32]; +} XnBistInfo; + +typedef struct XnFwLogMask +{ + uint32_t id; + char name[32]; +} XnFwLogMask; + +typedef struct XnUsbTestEndpointResult +{ + double averageBytesPerSecond; + uint32_t lostPackets; +} XnUsbTestEndpointResult; + +// Commands + +typedef struct XnCommandAHB +{ + uint32_t address; // Address of this register + uint32_t offsetInBits; // Offset of the field in bits within address + uint32_t widthInBits; // Width of the field in bits + uint32_t value; // For read requests, this is where the actual value will be filled. For write requests, the value to write. +} XnCommandAHB; + +typedef struct XnCommandI2C +{ + uint32_t deviceID; // Device to communicate with + uint32_t addressSize; // Size of the address, in bytes (1-4) + uint32_t address; // Address + uint32_t valueSize; // Size of the value, in bytes (1-4) + uint32_t mask; // For write request - a mask to be applied to the value. For read requests - ignored. + uint32_t value; // For write request - the value to be written. For read requests - the place where the actual value is written to +} XnCommandI2C; + +typedef struct XnCommandUploadFile +{ + const char* filePath; + uint32_t uploadToFactory; +} XnCommandUploadFile; + +typedef struct XnCommandDownloadFile +{ + uint16_t zone; + const char* firmwareFileName; + const char* targetPath; +} XnCommandDownloadFile; + +typedef struct XnCommandGetFileList +{ + uint32_t count; // in: number of allocated elements in files array. out: number of written elements in the array + XnFwFileEntry* files; +} XnCommandGetFileList; + +typedef struct XnCommandFormatZone +{ + uint8_t zone; +} XnCommandFormatZone; + +typedef struct XnCommandDumpEndpoint +{ + uint8_t endpoint; + bool enabled; +} XnCommandDumpEndpoint; + +typedef struct XnCommandGetI2CDeviceList +{ + uint32_t count; // in: number of allocated elements in devices array. out: number of written elements in the array + XnI2CDeviceInfo* devices; +} XnCommandGetI2CDeviceList; + +typedef struct XnCommandGetBistList +{ + uint32_t count; // in: number of allocated elements in tests array. out: number of written elements in the array + XnBistInfo* tests; +} XnCommandGetBistList; + +typedef struct XnCommandExecuteBist +{ + uint32_t id; + uint32_t errorCode; + uint32_t extraDataSize; // in: number of allocated bytes in extraData. out: number of written bytes in extraData + uint8_t* extraData; +} XnCommandExecuteBist; + +typedef struct XnCommandUsbTest +{ + uint32_t seconds; + uint32_t endpointCount; // in: number of allocated bytes in endpoints array. out: number of written bytes in array + XnUsbTestEndpointResult* endpoints; +} XnCommandUsbTest; + +typedef struct XnCommandGetLogMaskList +{ + uint32_t count; // in: number of allocated elements in masks array. out: number of written elements in the array + XnFwLogMask* masks; +} XnCommandGetLogMaskList; + +typedef struct XnCommandSetLogMaskState +{ + uint32_t mask; + bool enabled; +} XnCommandSetLogMaskState; + +#pragma pack (pop) + +#endif //_PRIME_SENSE_H_ \ No newline at end of file diff --git a/Include/Win32/OniPlatformWin32.h b/Include/Win32/OniPlatformWin32.h new file mode 100644 index 0000000..23bd81d --- /dev/null +++ b/Include/Win32/OniPlatformWin32.h @@ -0,0 +1,139 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_PLATFORM_WIN32_H_ +#define _ONI_PLATFORM_WIN32_H_ + +//--------------------------------------------------------------------------- +// Prerequisites +//--------------------------------------------------------------------------- +#ifndef WINVER // Allow use of features specific to Windows XP or later + #define WINVER 0x0501 +#endif +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later + #define _WIN32_WINNT 0x0501 +#endif +#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later + #define _WIN32_WINDOWS 0x0410 +#endif +#ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later + #define _WIN32_IE 0x0600 +#endif +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +// Undeprecate CRT functions +#ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE 1 +#endif + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _MSC_VER < 1600 // Visual Studio 2008 and older doesn't have stdint.h... +typedef signed char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef __int64 int64_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +//--------------------------------------------------------------------------- +// Platform Basic Definition +//--------------------------------------------------------------------------- +#define ONI_PLATFORM ONI_PLATFORM_WIN32 +#define ONI_PLATFORM_STRING "Win32" + +//--------------------------------------------------------------------------- +// Platform Capabilities +//--------------------------------------------------------------------------- +#define ONI_PLATFORM_ENDIAN_TYPE ONI_PLATFORM_IS_LITTLE_ENDIAN + +#define ONI_PLATFORM_SUPPORTS_DYNAMIC_LIBS 1 + +//--------------------------------------------------------------------------- +// Memory +//--------------------------------------------------------------------------- +/** The default memory alignment. */ +#define ONI_DEFAULT_MEM_ALIGN 16 + +/** The thread static declarator (using TLS). */ +#define ONI_THREAD_STATIC __declspec(thread) + +//--------------------------------------------------------------------------- +// Files +//--------------------------------------------------------------------------- +/** The maximum allowed file path size (in bytes). */ +#define ONI_FILE_MAX_PATH MAX_PATH + +//--------------------------------------------------------------------------- +// Call backs +//--------------------------------------------------------------------------- +/** The std call type. */ +#define ONI_STDCALL __stdcall + +/** The call back calling convention. */ +#define ONI_CALLBACK_TYPE ONI_STDCALL + +/** The C and C++ calling convension. */ +#define ONI_C_DECL __cdecl + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +/** Returns the date and time at compile time. */ +#define ONI_TIMESTAMP __DATE__ " " __TIME__ + +/** Converts n into a pre-processor string. */ +#define ONI_STRINGIFY(n) ONI_STRINGIFY_HELPER(n) +#define ONI_STRINGIFY_HELPER(n) #n + +//--------------------------------------------------------------------------- +// API Export/Import Macros +//--------------------------------------------------------------------------- +/** Indicates an exported shared library function. */ +#define ONI_API_EXPORT __declspec(dllexport) + +/** Indicates an imported shared library function. */ +#define ONI_API_IMPORT __declspec(dllimport) + +/** Indicates a deprecated function */ +#if _MSC_VER < 1400 // Before VS2005 there was no support for declspec deprecated... + #define ONI_API_DEPRECATED(msg) +#else + #define ONI_API_DEPRECATED(msg) __declspec(deprecated(msg)) +#endif + +#endif //_ONI_PLATFORM_WIN32_H_ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a436a84 --- /dev/null +++ b/Makefile @@ -0,0 +1,160 @@ +############################################################################# +# OpenNI makefile. +# +# default configuration is Release. for a debug version use: +# make CFG=Debug +# +# default compiler is g++. for another one use: +# make CXX= +# +# By default, CLR projects will only be build if mono is installed. +# To force CLR projects use: +# make FORCE_BUILD_CLR=1 +# +############################################################################# + +include ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +MAJOR_VERSION = $(shell grep "define ONI_VERSION_MAJOR" Include/OniVersion.h | cut -f 2) +MINOR_VERSION = $(shell grep "define ONI_VERSION_MINOR" Include/OniVersion.h | cut -f 2) +MAINT_VERSION = $(shell grep "define ONI_VERSION_MAINT" Include/OniVersion.h | cut -f 2) + +ifeq ("$(OSTYPE)","Darwin") + OS_NAME = MacOSX +else + OS_NAME = Linux +endif +PRODUCT_STRING = OpenNI-$(OS_NAME)-$(PLATFORM)-$(shell cd Packaging && python -c "import UpdateVersion; print UpdateVersion.getVersionName()" && cd ..) +FINAL_DIR = Packaging/Final + +OPENNI = Source/Core +XNLIB = ThirdParty/PSCommon/XnLib/Source +DEPTH_UTILS = Source/DepthUtils + +# list all drivers +ALL_DRIVERS = \ + Source/Drivers/DummyDevice \ + Source/Drivers/PS1080 \ + Source/Drivers/PSLink \ + Source/Drivers/OniFile + +# list all wrappers +ALL_WRAPPERS = \ + Wrappers/java/OpenNI.jni \ + Wrappers/java/OpenNI.java + +# list all tools +ALL_TOOLS = \ + Source/Drivers/PS1080/PS1080Console \ + Source/Drivers/PSLink/PSLinkConsole + +# list all core projects +ALL_CORE_PROJS = \ + $(XNLIB) \ + $(OPENNI) \ + $(DEPTH_UTILS) \ + $(ALL_DRIVERS) \ + $(ALL_WRAPPERS) \ + $(ALL_TOOLS) + +# list all samples +CORE_SAMPLES = \ + Samples/SimpleRead \ + Samples/EventBasedRead \ + Samples/MultipleStreamRead \ + Samples/MWClosestPoint \ + Samples/MWClosestPointApp + +# list all java samples +JAVA_SAMPLES = \ + Samples/SimpleViewer.java + +ifeq "$(GLUT_SUPPORTED)" "1" + ALL_TOOLS += \ + Source/Tools/NiViewer + + CORE_SAMPLES += \ + Samples/SimpleViewer \ + Samples/MultiDepthViewer \ + Samples/ClosestPointViewer +else + ifeq "$(GLES_SUPPORTED)" "1" + CORE_SAMPLES += + endif +endif + +ALL_SAMPLES = \ + $(CORE_SAMPLES) \ + $(JAVA_SAMPLES) + +# list all projects that are build +ALL_BUILD_PROJS = \ + $(ALL_CORE_PROJS) \ + $(ALL_SAMPLES) + +ALL_PROJS = \ + $(ALL_BUILD_PROJS) + +ALL_PROJS_CLEAN = $(foreach proj,$(ALL_PROJS),$(proj)-clean) + +# define a function which creates a target for each proj +define CREATE_PROJ_TARGET +$1: + $$(MAKE) -C $1 + +$1-clean: + $$(MAKE) -C $1 clean +endef + +################ TARGETS ################## + +.PHONY: all $(ALL_PROJS) $(ALL_PROJS_CLEAN) install uninstall clean release + +# make all makefiles +all: $(ALL_PROJS) + +core: $(ALL_CORE_PROJS) + +samples: $(ALL_SAMPLES) + +# create projects targets +$(foreach proj,$(ALL_PROJS),$(eval $(call CREATE_PROJ_TARGET,$(proj)))) + +# additional dependencies +$(OPENNI): $(XNLIB) +Wrappers/java/OpenNI.jni: $(OPENNI) $(XNLIB) + +Source/Drivers/DummyDevice: $(OPENNI) $(XNLIB) +Source/Drivers/RawDevice: $(OPENNI) $(XNLIB) +Source/Drivers/PS1080: $(OPENNI) $(XNLIB) $(DEPTH_UTILS) +Source/Drivers/PS1080/PS1080Console: $(OPENNI) $(XNLIB) +Source/Drivers/PSLink: $(OPENNI) $(XNLIB) +Source/Drivers/PSLink/PSLinkConsole: $(OPENNI) $(XNLIB) +Source/Drivers/OniFile: $(OPENNI) $(XNLIB) + +Source/Tools/NiViewer: $(OPENNI) $(XNLIB) + +Samples/SimpleRead: $(OPENNI) +Samples/EventBasedRead: $(OPENNI) +Samples/MultipleStreamRead: $(OPENNI) +Samples/MWClosestPoint: $(OPENNI) +Samples/MWClosestPointApp: $(OPENNI) Samples/MWClosestPoint + +Samples/SimpleViewer: $(OPENNI) +Samples/MultiDepthViewer: $(OPENNI) +Samples/ClosestPointViewer: $(OPENNI) Samples/MWClosestPoint +Samples/SimpleViewer.java: Wrappers/java/OpenNI.java + +$(FINAL_DIR): + mkdir -p $(FINAL_DIR) + +doc: + Source/Documentation/Runme.py + rm -f Source/Documentation/html/*.md5 + +release: | all doc $(FINAL_DIR) + Packaging/Harvest.py Packaging/$(PRODUCT_STRING) $(PLATFORM) + cd Packaging; tar -cjf Final/$(PRODUCT_STRING).tar.bz2 $(PRODUCT_STRING) + +# clean is cleaning all projects +clean: $(ALL_PROJS_CLEAN) diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..b8aa8b1 --- /dev/null +++ b/NOTICE @@ -0,0 +1,15 @@ +OpenNI 2.0 +Copyright (c) 2012 PrimeSense Ltd. + +This product is licensed under the Apache License, Version 2.0 (the "License"); + +You should have received a copy of the Apache License +along with OpenNI. If not, see: + http://www.apache.org/licenses/LICENSE-2.0 + +This software is based in part on the work of the Independent JPEG Group. + +Your use of the library may involve use of the Microsoft Kinect for Windows Software Development Kit, +which is currently subject to the following license: + http://www.microsoft.com/en-us/kinectforwindows/develop/sdk-eula.aspx + diff --git a/OpenNI.sln b/OpenNI.sln new file mode 100644 index 0000000..9af176a --- /dev/null +++ b/OpenNI.sln @@ -0,0 +1,327 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleRead", "Samples\SimpleRead\SimpleRead.vcxproj", "{BDA3BF24-550A-4BF9-83E5-7B56134EDD40}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenNI", "Source\Core\OpenNI.vcxproj", "{72D595BB-8C52-449B-91DB-0E9F6AEAF53A}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XnLib", "ThirdParty\PSCommon\XnLib\Source\XnLib.vcxproj", "{72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{EA8ECD36-D9CB-4860-97A7-2D85E7111E6B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DummyDevice", "Source\Drivers\DummyDevice\DummyDevice.vcxproj", "{B7DE6235-086E-42C6-B5AC-2DC795388ED9}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleViewer", "Samples\SimpleViewer\SimpleViewer.vcxproj", "{BDA3BF24-550A-4BF9-83E5-7B56134EED40}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devices", "Devices", "{238D091D-1A85-4A61-9DCD-483768C51804}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{20285393-1DB1-4300-8AD3-30AEAE3C5DA6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NiViewer", "Source\Tools\NiViewer\NiViewer.vcxproj", "{BDA3BF24-550A-4BF9-83E5-0056134EED40}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MultiDepthViewer", "Samples\MultiDepthViewer\MultiDepthViewer.vcxproj", "{BDA3BF24-550A-FFF9-83E5-7B56134EEE40}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PS1080", "Source\Drivers\PS1080\PS1080.vcxproj", "{9F6652AF-35F2-452E-A2D3-08D05F5C075E}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB} = {72D595BB-8C52-449B-91DB-0E9F6AEABBBB} + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestDevice", "Source\Drivers\TestDevice\TestDevice.vcxproj", "{31F0F25B-A84A-48AC-9716-5DF9137F3855}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OniFile", "Source\Drivers\OniFile\OniFile.vcxproj", "{15ECC029-90DE-4D1D-B00A-4A8E647D8C24}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MWClosestPoint", "Samples\MWClosestPoint\MWClosestPoint.vcxproj", "{4B01E59D-CC85-4B2E-B3A2-00F75856CB23}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MWClosestPointApp", "Samples\MWClosestPointApp\MWClosestPointApp.vcxproj", "{A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}" + ProjectSection(ProjectDependencies) = postProject + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23} = {4B01E59D-CC85-4B2E-B3A2-00F75856CB23} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ClosestPointViewer", "Samples\ClosestPointViewer\ClosestPointViewer.vcxproj", "{BDA3BF24-550A-4BF9-83E5-0006134EED40}" + ProjectSection(ProjectDependencies) = postProject + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23} = {4B01E59D-CC85-4B2E-B3A2-00F75856CB23} + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EventBasedRead", "Samples\EventBasedRead\EventBasedRead.vcxproj", "{BDA3BF24-5555-4BF9-83E5-7B56134EDD40}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MultipleStreamRead", "Samples\MultipleStreamRead\MultipleStreamRead.vcxproj", "{920D08AC-452C-4326-BC6E-86FE65848587}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Install", "Packaging\Install\Install.wixproj", "{BAEB9C48-562C-4D56-A6CD-18932265480A}" + ProjectSection(ProjectDependencies) = postProject + {5B74F010-8B79-46B5-B906-C2B56CDB3386} = {5B74F010-8B79-46B5-B906-C2B56CDB3386} + {D5709FB9-909D-415F-8F86-2F25BEF6CE23} = {D5709FB9-909D-415F-8F86-2F25BEF6CE23} + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63} = {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63} + {E636BACA-795F-41CF-BC52-14C727BF014E} = {E636BACA-795F-41CF-BC52-14C727BF014E} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Kinect", "Source\Drivers\Kinect\Kinect.vcxproj", "{E636BACA-795F-41CF-BC52-14C727BF014E}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DepthUtils", "Source\DepthUtils\DepthUtils.vcxproj", "{72D595BB-8C52-449B-91DB-0E9F6AEABBBB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PSLinkConsole", "Source\Drivers\PSLink\PSLinkConsole\PSLinkConsole.vcxproj", "{D39A4248-3985-41DE-AFD5-AEC58D29291F}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wrappers", "Wrappers", "{9FCEE1ED-B0D9-46D7-A014-98FBDD0EF6A9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenNI.jni", "Wrappers\java\OpenNI.jni\OpenNI.jni.vcxproj", "{1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PS1080Console", "Source\Drivers\PS1080\PS1080Console\PS1080Console.vcxproj", "{D5709FB9-909D-415F-8F86-2F25BEF6CE23}" + ProjectSection(ProjectDependencies) = postProject + {9F6652AF-35F2-452E-A2D3-08D05F5C075E} = {9F6652AF-35F2-452E-A2D3-08D05F5C075E} + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} = {72D595BB-8C52-449B-91DB-0E9F6AEAF53A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PSLink", "Source\Drivers\PSLink\PSLink.vcxproj", "{5B74F010-8B79-46B5-B906-C2B56CDB3386}" + ProjectSection(ProjectDependencies) = postProject + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} = {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Debug|x64.ActiveCfg = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Debug|x64.Build.0 = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Debug|x86.ActiveCfg = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Debug|x86.Build.0 = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Release|x64.ActiveCfg = Release|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Release|x64.Build.0 = Release|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Release|x86.ActiveCfg = Release|Win32 + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40}.Release|x86.Build.0 = Release|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Debug|x64.ActiveCfg = Debug|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Debug|x64.Build.0 = Debug|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Debug|x86.ActiveCfg = Debug|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Debug|x86.Build.0 = Debug|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Release|x64.ActiveCfg = Release|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Release|x64.Build.0 = Release|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Release|x86.ActiveCfg = Release|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF53A}.Release|x86.Build.0 = Release|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Debug|x64.ActiveCfg = Debug|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Debug|x64.Build.0 = Debug|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Debug|x86.ActiveCfg = Debug|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Debug|x86.Build.0 = Debug|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Release|x64.ActiveCfg = Release|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Release|x64.Build.0 = Release|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Release|x86.ActiveCfg = Release|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEAF5BB}.Release|x86.Build.0 = Release|Win32 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Debug|x64.ActiveCfg = Debug|x64 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Debug|x64.Build.0 = Debug|x64 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Debug|x86.ActiveCfg = Debug|Win32 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Debug|x86.Build.0 = Debug|Win32 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Release|x64.ActiveCfg = Release|x64 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Release|x64.Build.0 = Release|x64 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Release|x86.ActiveCfg = Release|Win32 + {B7DE6235-086E-42C6-B5AC-2DC795388ED9}.Release|x86.Build.0 = Release|Win32 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Debug|x64.ActiveCfg = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Debug|x64.Build.0 = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Debug|x86.ActiveCfg = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Debug|x86.Build.0 = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Release|x64.ActiveCfg = Release|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Release|x64.Build.0 = Release|x64 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Release|x86.ActiveCfg = Release|Win32 + {BDA3BF24-550A-4BF9-83E5-7B56134EED40}.Release|x86.Build.0 = Release|Win32 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Debug|x64.ActiveCfg = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Debug|x64.Build.0 = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Debug|x86.ActiveCfg = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Debug|x86.Build.0 = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Release|x64.ActiveCfg = Release|x64 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Release|x64.Build.0 = Release|x64 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Release|x86.ActiveCfg = Release|Win32 + {BDA3BF24-550A-4BF9-83E5-0056134EED40}.Release|x86.Build.0 = Release|Win32 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Debug|x64.ActiveCfg = Debug|x64 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Debug|x64.Build.0 = Debug|x64 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Debug|x86.ActiveCfg = Debug|Win32 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Debug|x86.Build.0 = Debug|Win32 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Release|x64.ActiveCfg = Release|x64 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Release|x64.Build.0 = Release|x64 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Release|x86.ActiveCfg = Release|Win32 + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40}.Release|x86.Build.0 = Release|Win32 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Debug|x64.ActiveCfg = Debug|x64 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Debug|x64.Build.0 = Debug|x64 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Debug|x86.ActiveCfg = Debug|Win32 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Debug|x86.Build.0 = Debug|Win32 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Release|x64.ActiveCfg = Release|x64 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Release|x64.Build.0 = Release|x64 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Release|x86.ActiveCfg = Release|Win32 + {9F6652AF-35F2-452E-A2D3-08D05F5C075E}.Release|x86.Build.0 = Release|Win32 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Debug|x64.ActiveCfg = Debug|x64 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Debug|x64.Build.0 = Debug|x64 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Debug|x86.ActiveCfg = Debug|Win32 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Debug|x86.Build.0 = Debug|Win32 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Release|x64.ActiveCfg = Release|x64 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Release|x64.Build.0 = Release|x64 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Release|x86.ActiveCfg = Release|Win32 + {31F0F25B-A84A-48AC-9716-5DF9137F3855}.Release|x86.Build.0 = Release|Win32 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Debug|x64.ActiveCfg = Debug|x64 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Debug|x64.Build.0 = Debug|x64 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Debug|x86.ActiveCfg = Debug|Win32 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Debug|x86.Build.0 = Debug|Win32 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Release|x64.ActiveCfg = Release|x64 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Release|x64.Build.0 = Release|x64 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Release|x86.ActiveCfg = Release|Win32 + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24}.Release|x86.Build.0 = Release|Win32 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Debug|x64.ActiveCfg = Debug|x64 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Debug|x64.Build.0 = Debug|x64 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Debug|x86.ActiveCfg = Debug|Win32 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Debug|x86.Build.0 = Debug|Win32 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Release|x64.ActiveCfg = Release|x64 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Release|x64.Build.0 = Release|x64 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Release|x86.ActiveCfg = Release|Win32 + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23}.Release|x86.Build.0 = Release|Win32 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Debug|x64.ActiveCfg = Debug|x64 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Debug|x64.Build.0 = Debug|x64 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Debug|x86.ActiveCfg = Debug|Win32 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Debug|x86.Build.0 = Debug|Win32 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Release|x64.ActiveCfg = Release|x64 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Release|x64.Build.0 = Release|x64 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Release|x86.ActiveCfg = Release|Win32 + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E}.Release|x86.Build.0 = Release|Win32 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Debug|x64.ActiveCfg = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Debug|x64.Build.0 = Debug|x64 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Debug|x86.ActiveCfg = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Debug|x86.Build.0 = Debug|Win32 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Release|x64.ActiveCfg = Release|x64 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Release|x64.Build.0 = Release|x64 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Release|x86.ActiveCfg = Release|Win32 + {BDA3BF24-550A-4BF9-83E5-0006134EED40}.Release|x86.Build.0 = Release|Win32 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Debug|x64.ActiveCfg = Debug|x64 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Debug|x64.Build.0 = Debug|x64 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Debug|x86.ActiveCfg = Debug|Win32 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Debug|x86.Build.0 = Debug|Win32 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Release|x64.ActiveCfg = Release|x64 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Release|x64.Build.0 = Release|x64 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Release|x86.ActiveCfg = Release|Win32 + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40}.Release|x86.Build.0 = Release|Win32 + {920D08AC-452C-4326-BC6E-86FE65848587}.Debug|x64.ActiveCfg = Debug|x64 + {920D08AC-452C-4326-BC6E-86FE65848587}.Debug|x64.Build.0 = Debug|x64 + {920D08AC-452C-4326-BC6E-86FE65848587}.Debug|x86.ActiveCfg = Debug|Win32 + {920D08AC-452C-4326-BC6E-86FE65848587}.Debug|x86.Build.0 = Debug|Win32 + {920D08AC-452C-4326-BC6E-86FE65848587}.Release|x64.ActiveCfg = Release|x64 + {920D08AC-452C-4326-BC6E-86FE65848587}.Release|x64.Build.0 = Release|x64 + {920D08AC-452C-4326-BC6E-86FE65848587}.Release|x86.ActiveCfg = Release|Win32 + {920D08AC-452C-4326-BC6E-86FE65848587}.Release|x86.Build.0 = Release|Win32 + {BAEB9C48-562C-4D56-A6CD-18932265480A}.Debug|x64.ActiveCfg = Debug|x64 + {BAEB9C48-562C-4D56-A6CD-18932265480A}.Debug|x86.ActiveCfg = Debug|x86 + {BAEB9C48-562C-4D56-A6CD-18932265480A}.Release|x64.ActiveCfg = Release|x64 + {BAEB9C48-562C-4D56-A6CD-18932265480A}.Release|x86.ActiveCfg = Release|x86 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Debug|x64.ActiveCfg = Debug|x64 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Debug|x64.Build.0 = Debug|x64 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Debug|x86.ActiveCfg = Debug|Win32 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Debug|x86.Build.0 = Debug|Win32 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Release|x64.ActiveCfg = Release|x64 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Release|x64.Build.0 = Release|x64 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Release|x86.ActiveCfg = Release|Win32 + {E636BACA-795F-41CF-BC52-14C727BF014E}.Release|x86.Build.0 = Release|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Debug|x64.ActiveCfg = Debug|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Debug|x64.Build.0 = Debug|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Debug|x86.ActiveCfg = Debug|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Debug|x86.Build.0 = Debug|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Release|x64.ActiveCfg = Release|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Release|x64.Build.0 = Release|x64 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Release|x86.ActiveCfg = Release|Win32 + {72D595BB-8C52-449B-91DB-0E9F6AEABBBB}.Release|x86.Build.0 = Release|Win32 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Debug|x64.ActiveCfg = Debug|x64 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Debug|x64.Build.0 = Debug|x64 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Debug|x86.ActiveCfg = Debug|Win32 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Debug|x86.Build.0 = Debug|Win32 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Release|x64.ActiveCfg = Release|x64 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Release|x64.Build.0 = Release|x64 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Release|x86.ActiveCfg = Release|Win32 + {D39A4248-3985-41DE-AFD5-AEC58D29291F}.Release|x86.Build.0 = Release|Win32 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Debug|x64.ActiveCfg = Debug|x64 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Debug|x64.Build.0 = Debug|x64 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Debug|x86.ActiveCfg = Debug|Win32 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Debug|x86.Build.0 = Debug|Win32 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Release|x64.ActiveCfg = Release|x64 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Release|x64.Build.0 = Release|x64 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Release|x86.ActiveCfg = Release|Win32 + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63}.Release|x86.Build.0 = Release|Win32 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Debug|x64.ActiveCfg = Debug|x64 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Debug|x64.Build.0 = Debug|x64 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Debug|x86.ActiveCfg = Debug|Win32 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Debug|x86.Build.0 = Debug|Win32 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Release|x64.ActiveCfg = Release|x64 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Release|x64.Build.0 = Release|x64 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Release|x86.ActiveCfg = Release|Win32 + {D5709FB9-909D-415F-8F86-2F25BEF6CE23}.Release|x86.Build.0 = Release|Win32 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Debug|x64.ActiveCfg = Debug|x64 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Debug|x64.Build.0 = Debug|x64 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Debug|x86.ActiveCfg = Debug|Win32 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Debug|x86.Build.0 = Debug|Win32 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Release|x64.ActiveCfg = Release|x64 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Release|x64.Build.0 = Release|x64 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Release|x86.ActiveCfg = Release|Win32 + {5B74F010-8B79-46B5-B906-C2B56CDB3386}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {BDA3BF24-550A-4BF9-83E5-7B56134EED40} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {BDA3BF24-550A-4BF9-83E5-0006134EED40} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {920D08AC-452C-4326-BC6E-86FE65848587} = {EA8ECD36-D9CB-4860-97A7-2D85E7111E6B} + {B7DE6235-086E-42C6-B5AC-2DC795388ED9} = {238D091D-1A85-4A61-9DCD-483768C51804} + {9F6652AF-35F2-452E-A2D3-08D05F5C075E} = {238D091D-1A85-4A61-9DCD-483768C51804} + {31F0F25B-A84A-48AC-9716-5DF9137F3855} = {238D091D-1A85-4A61-9DCD-483768C51804} + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24} = {238D091D-1A85-4A61-9DCD-483768C51804} + {E636BACA-795F-41CF-BC52-14C727BF014E} = {238D091D-1A85-4A61-9DCD-483768C51804} + {5B74F010-8B79-46B5-B906-C2B56CDB3386} = {238D091D-1A85-4A61-9DCD-483768C51804} + {BDA3BF24-550A-4BF9-83E5-0056134EED40} = {20285393-1DB1-4300-8AD3-30AEAE3C5DA6} + {D39A4248-3985-41DE-AFD5-AEC58D29291F} = {20285393-1DB1-4300-8AD3-30AEAE3C5DA6} + {D5709FB9-909D-415F-8F86-2F25BEF6CE23} = {20285393-1DB1-4300-8AD3-30AEAE3C5DA6} + {1723CBBA-8EE7-439A-93FB-2E94B4DB2E63} = {9FCEE1ED-B0D9-46D7-A014-98FBDD0EF6A9} + EndGlobalSection +EndGlobal diff --git a/Packaging/Harvest.py b/Packaging/Harvest.py new file mode 100755 index 0000000..4ce9ed2 --- /dev/null +++ b/Packaging/Harvest.py @@ -0,0 +1,343 @@ +#!/usr/bin/python + +#/**************************************************************************** +#* * +#* OpenNI 2.x Alpha * +#* Copyright (C) 2012 PrimeSense Ltd. * +#* * +#* This file is part of OpenNI. * +#* * +#* Licensed under the Apache License, Version 2.0 (the "License"); * +#* you may not use this file except in compliance with the License. * +#* You may obtain a copy of the License at * +#* * +#* http://www.apache.org/licenses/LICENSE-2.0 * +#* * +#* Unless required by applicable law or agreed to in writing, software * +#* distributed under the License is distributed on an "AS IS" BASIS, * +#* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +#* See the License for the specific language governing permissions and * +#* limitations under the License. * +#* * +#****************************************************************************/ +import os +import re +import sys +import shutil +import platform +import stat +import xml.dom.minidom + +class Harvest: + def __init__(self, rootDir, outDir, arch): + self.rootDir = rootDir + self.outDir = outDir + self.arch = arch + self.osName = platform.system() + self.binDir = os.path.join(rootDir, 'Bin', arch + '-Release') + self.platformSuffix = '' + self.glutSuffix = '32' + + if self.osName == 'Windows': + if arch == 'x86': + self.binDir = os.path.join(rootDir, 'Bin', 'Win32-Release') + elif arch == 'x64': + self.platformSuffix = '64' + self.glutSuffix = '64' + + def copySharedObject(self, sourceDir, name, targetDir): + if self.osName == 'Windows': + shutil.copy(os.path.join(sourceDir, name + '.dll'), targetDir) + shutil.copy(os.path.join(sourceDir, name + '.pdb'), targetDir) + elif self.osName == 'Linux': + shutil.copy(os.path.join(sourceDir, 'lib' + name + '.so'), targetDir) + elif self.osName == 'Darwin': + shutil.copy(os.path.join(sourceDir, 'lib' + name + '.dylib'), targetDir) + else: + raise 'Unsupported platform!' + + def copyExecutable(self, sourceDir, name, targetDir): + if self.osName == 'Windows': + shutil.copy(os.path.join(sourceDir, name + '.exe'), targetDir) + shutil.copy(os.path.join(sourceDir, name + '.pdb'), targetDir) + else: + shutil.copy(os.path.join(sourceDir, name), targetDir) + + def regxReplace(self, findStr, repStr, filePath): + "replaces all findStr by repStr in file filePath using regualr expression" + findStrRegx = re.compile(findStr) + tempName = filePath+'~~~' + fileMode = os.stat(filePath).st_mode + os.chmod(filePath, fileMode | stat.S_IWRITE) + input = open(filePath) + output = open(tempName, 'w') + for s in input: + output.write(findStrRegx.sub(repStr, s)) + output.close() + input.close() + os.remove(filePath) + os.rename(tempName, filePath) + + def copyRedistFiles(self, targetDir): + os.makedirs(targetDir) + # start with OpenNI itself + self.copySharedObject(self.binDir, 'OpenNI2', targetDir) + self.copySharedObject(self.binDir, 'OpenNI2.jni', targetDir) + shutil.copy(os.path.join(self.binDir, 'org.openni.jar'), targetDir) + shutil.copy(os.path.join(self.rootDir, 'Config', 'OpenNI.ini'), targetDir) + # and now all drivers + binDriversDir = os.path.join(self.binDir, 'OpenNI2', 'Drivers') + targetDriversDir = os.path.join(targetDir, 'OpenNI2', 'Drivers') + os.makedirs(targetDriversDir) + self.copySharedObject(binDriversDir, 'OniFile', targetDriversDir) + self.copySharedObject(binDriversDir, 'PS1080', targetDriversDir) + self.copySharedObject(binDriversDir, 'PSLink', targetDriversDir) + shutil.copy(os.path.join(self.rootDir, 'Config', 'OpenNI2', 'Drivers', 'PS1080.ini'), targetDriversDir) + shutil.copy(os.path.join(self.rootDir, 'Config', 'OpenNI2', 'Drivers', 'PSLink.ini'), targetDriversDir) + if self.osName == 'Windows': + self.copySharedObject(binDriversDir, 'Kinect', targetDriversDir) + + def copySample(self, samplesDir, name, isLibrary = False, isGL = False, isJava = False): + if self.arch == 'Arm' and isGL: + return + + sampleTargetDir = os.path.join(samplesDir, name) + sampleSourceDir = os.path.join(self.rootDir, 'Samples', name) + + # copy sources + for root, dirs, files in os.walk(sampleSourceDir): + # take dir name without 'root' and append to target + dst = os.path.join(samplesDir, name, os.path.relpath(root, sampleSourceDir)) + #print dst + for file in files: + if (isJava and file.endswith('.java')) or (not isJava and (file.endswith('.h') or file.endswith('.cpp'))): + if not os.path.exists(dst): + os.makedirs(dst) + shutil.copy(os.path.join(root, file), dst) + + # copy common header + if not isJava and not isLibrary: + shutil.copy(os.path.join(self.rootDir, 'Samples', 'Common', 'OniSampleUtilities.h'), sampleTargetDir) + + # copy GL headers + if self.osName == 'Windows' and isGL: + shutil.copytree(os.path.join(self.rootDir, 'ThirdParty', 'GL', 'GL'), os.path.join(sampleTargetDir, 'GL')) + shutil.copytree(os.path.join(self.rootDir, 'ThirdParty', 'GL', 'glh'), os.path.join(sampleTargetDir, 'glh')) + # and lib + shutil.copy(os.path.join(self.rootDir, 'ThirdParty', 'GL', 'glut32.lib'), sampleTargetDir) + shutil.copy(os.path.join(self.rootDir, 'ThirdParty', 'GL', 'glut64.lib'), sampleTargetDir) + shutil.copy(os.path.join(self.rootDir, 'ThirdParty', 'GL', 'glut32.dll'), sampleTargetDir) + shutil.copy(os.path.join(self.rootDir, 'ThirdParty', 'GL', 'glut64.dll'), sampleTargetDir) + + # and project file / makefile + if self.osName == 'Windows': + if isJava: + shutil.copy(os.path.join(sampleSourceDir, 'Build.bat'), sampleTargetDir) + shutil.copy(os.path.join(self.rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'BuildJavaWindows.py'), sampleTargetDir) + # fix call + buildFile = open(os.path.join(sampleTargetDir, 'Build.bat')) + buildScript = buildFile.read() + buildFile.close() + + buildScript = re.sub('..\\\\..\\\\ThirdParty\\\\PSCommon\\\\BuildSystem\\\\', '', buildScript) + buildScript = re.sub('..\\\\..\\\\Bin', 'Bin', buildScript) + + buildFile = open(os.path.join(sampleTargetDir, 'Build.bat'), 'w') + buildFile.write('@echo off\n') + buildFile.write('IF "%1"=="x64" (\n') + buildFile.write('\txcopy /D /S /F /Y "%OPENNI2_REDIST64%\\*" "Bin\\x64-Release\\"\n') + buildFile.write(') ELSE (\n') + buildFile.write('\txcopy /D /S /F /Y "%OPENNI2_REDIST%\\*" "Bin\\Win32-Release\\"\n') + buildFile.write(')\n') + buildFile.write(buildScript) + buildFile.close() + + else: + shutil.copy(os.path.join(sampleSourceDir, name + '.vcxproj'), sampleTargetDir) + projFile = os.path.join(sampleTargetDir, name + '.vcxproj') + #ET.register_namespace('', 'http://schemas.microsoft.com/developer/msbuild/2003') + doc = xml.dom.minidom.parse(projFile) + + # remove OutDir and IntDir (make them default) + for propertyGroup in doc.getElementsByTagName("PropertyGroup"): + if len(propertyGroup.getElementsByTagName("OutDir")) > 0: + propertyGroup.parentNode.removeChild(propertyGroup) + + for group in doc.getElementsByTagName("ItemDefinitionGroup"): + condAttr = group.getAttribute('Condition') + if condAttr.find('x64') != -1: + postfix = '64' + glPostfix = '64' + else: + postfix = '' + glPostfix = '32' + + incDirs = group.getElementsByTagName('ClCompile')[0].getElementsByTagName('AdditionalIncludeDirectories')[0] + val = incDirs.firstChild.data + + # fix GL include dir + val = re.sub('..\\\\..\\\\ThirdParty\\\\GL', r'.', val) + # fix Common include dir + val = re.sub('..\\\\Common', r'.', val) + # fix OpenNI include dir + val = re.sub('..\\\\..\\\\Include', '$(OPENNI2_INCLUDE' + postfix + ')', val) + + incDirs.firstChild.data = val + + # fix additional library directories + libDirs = group.getElementsByTagName('Link')[0].getElementsByTagName('AdditionalLibraryDirectories')[0] + val = libDirs.firstChild.data + val = re.sub('\$\(OutDir\)', '$(OutDir);$(OPENNI2_LIB' + postfix + ')', val) + libDirs.firstChild.data = val + + # add post-build event to copy OpenNI redist + post = doc.createElement('PostBuildEvent') + cmd = 'xcopy /D /S /F /Y "$(OPENNI2_REDIST' + postfix + ')\*" "$(OutDir)"\n' + if isGL: + cmd += 'xcopy /D /F /Y "$(ProjectDir)\\glut' + glPostfix + '.dll" "$(OutDir)"\n' + + cmdNode = doc.createElement('Command') + cmdNode.appendChild(doc.createTextNode(cmd)) + post.appendChild(cmdNode) + group.appendChild(post) + + proj = open(projFile, 'w') + proj.write(doc.toxml()) + proj.close() + + elif self.osName == 'Linux' or self.osName == 'Darwin': + shutil.copy(os.path.join(sampleSourceDir, 'Makefile'), sampleTargetDir) + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'CommonDefs.mak'), sampleTargetDir) + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'CommonTargets.mak'), sampleTargetDir) + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'Platform.x86'), sampleTargetDir) + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'Platform.x64'), sampleTargetDir) + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'Platform.Arm'), sampleTargetDir) + if isJava: + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'CommonJavaMakefile'), sampleTargetDir) + else: + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'BuildSystem', 'CommonCppMakefile'), sampleTargetDir) + + # fix common makefiles path + self.regxReplace('../../ThirdParty/PSCommon/BuildSystem/', '', os.path.join(sampleTargetDir, 'Makefile')) + + # fix BIN dir + self.regxReplace('BIN_DIR = ../../Bin', 'BIN_DIR = Bin', os.path.join(sampleTargetDir, 'Makefile')) + + # fix include dirs and copy openni_redist + add = r''' +ifndef OPENNI2_INCLUDE + $(error OPENNI2_INCLUDE is not defined. Please define it or 'source' the OpenNIDevEnvironment file from the installation) +else ifndef OPENNI2_REDIST + $(error OPENNI2_REDIST is not defined. Please define it or 'source' the OpenNIDevEnvironment file from the installation) +endif + +INC_DIRS += $(OPENNI2_INCLUDE) + +include \1 + +.PHONY: copy-redist +copy-redist: + cp -R $(OPENNI2_REDIST)/* $(OUT_DIR) + +$(OUTPUT_FILE): copy-redist +''' + self.regxReplace(r'include (Common.*Makefile)', add, os.path.join(sampleTargetDir, 'Makefile')) + + # and executable + if isJava: + splitName = os.path.splitext(name) + # copy jar + shutil.copy(os.path.join(self.binDir, 'org.openni.Samples.' + splitName[0] + '.jar'), os.path.join(samplesDir, 'Bin')) + # and script + if not isLibrary: + if self.osName == 'Windows': + shutil.copy(os.path.join(self.binDir, 'org.openni.Samples.' + splitName[0] + '.bat'), os.path.join(samplesDir, 'Bin')) + else: + shutil.copy(os.path.join(self.binDir, 'org.openni.Samples.' + splitName[0]), os.path.join(samplesDir, 'Bin')) + elif isLibrary: + self.copySharedObject(self.binDir, name, os.path.join(samplesDir, 'Bin')) + if self.osName == 'Windows': + shutil.copy(os.path.join(self.binDir, name + '.lib'), os.path.join(samplesDir, 'Bin')) + else: # regular executable + self.copyExecutable(self.binDir, name, os.path.join(samplesDir, 'Bin')) + + def copyTool(self, toolsDir, name, isGL = False): + if self.arch == 'Arm' and isGL: + return + + self.copyExecutable(self.binDir, name, toolsDir) + + def copyDocumentation(self, docDir): + if self.osName == 'Windows': + os.makedirs(docDir) + shutil.copy(os.path.join(self.rootDir, 'Source', 'Documentation', 'html', 'OpenNI.chm'), docDir) + else: + shutil.copytree(os.path.join(self.rootDir, 'Source', 'Documentation', 'html'), docDir) + + shutil.copytree(os.path.join(self.rootDir, 'Source', 'Documentation', 'java'), os.path.join(docDir, 'java')) + + def copyGLUT(self, targetDir): + if self.osName == 'Windows': + shutil.copy(os.path.join(rootDir, 'ThirdParty', 'GL', 'glut' + self.glutSuffix + '.dll'), targetDir) + + def run(self): + if os.path.exists(self.outDir): + shutil.rmtree(self.outDir) + os.makedirs(self.outDir) + + # Redist + self.copyRedistFiles(os.path.join(self.outDir, 'Redist')) + + # Samples + samplesDir = os.path.join(self.outDir, 'Samples') + self.copyRedistFiles(os.path.join(samplesDir, 'Bin')) + self.copyGLUT(os.path.join(samplesDir, 'Bin')) + self.copySample(samplesDir, 'SimpleRead') + self.copySample(samplesDir, 'SimpleViewer', isGL = True) + self.copySample(samplesDir, 'SimpleViewer.java', isJava = True) + self.copySample(samplesDir, 'EventBasedRead') + self.copySample(samplesDir, 'MultiDepthViewer', isGL = True) + self.copySample(samplesDir, 'MultipleStreamRead') + self.copySample(samplesDir, 'MWClosestPoint', isLibrary = True) + self.copySample(samplesDir, 'MWClosestPointApp') + self.copySample(samplesDir, 'ClosestPointViewer', isGL = True) + + # Tools + toolsDir = os.path.join(self.outDir, 'Tools') + self.copyRedistFiles(toolsDir) + self.copyGLUT(toolsDir) + self.copyTool(toolsDir, 'NiViewer', isGL = True) + self.copyTool(toolsDir, 'PS1080Console') + self.copyTool(toolsDir, 'PSLinkConsole') + + # Documentation + docDir = os.path.join(self.outDir, 'Documentation') + self.copyDocumentation(docDir) + + # Include + shutil.copytree(os.path.join(rootDir, 'Include'), os.path.join(self.outDir, 'Include')) + + # Licenses + shutil.copy(os.path.join(rootDir, 'NOTICE'), self.outDir) + shutil.copy(os.path.join(rootDir, 'LICENSE'), self.outDir) + + if self.osName == 'Windows': + # Driver + shutil.copytree(os.path.join(rootDir, 'ThirdParty', 'PSCommon', 'XnLib', 'Driver', 'Win32', 'Bin'), os.path.join(self.outDir, 'Driver')) + + # Library + libDir = os.path.join(self.outDir, 'Lib') + os.makedirs(libDir) + shutil.copy(os.path.join(self.binDir, 'OpenNI2.lib'), libDir) + else: + # install script + shutil.copy(os.path.join(self.rootDir, 'Packaging', 'Linux', 'install.sh'), self.outDir) + shutil.copy(os.path.join(self.rootDir, 'Packaging', 'Linux', 'primesense-usb.rules'), self.outDir) + +if len(sys.argv) < 3: + print 'Usage: ' + sys.argv[0] + ' ' + exit(1) + +rootDir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) +harvest = Harvest(rootDir, sys.argv[1], sys.argv[2]) +harvest.run() diff --git a/Packaging/Install/Fragments/.gitignore b/Packaging/Install/Fragments/.gitignore new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/Packaging/Install/Fragments/.gitignore @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Packaging/Install/Includes/Variables.wxi b/Packaging/Install/Includes/Variables.wxi new file mode 100644 index 0000000..06d0174 --- /dev/null +++ b/Packaging/Install/Includes/Variables.wxi @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Packaging/Install/Install.wixproj b/Packaging/Install/Install.wixproj new file mode 100644 index 0000000..933565a --- /dev/null +++ b/Packaging/Install/Install.wixproj @@ -0,0 +1,226 @@ + + + + True + Release + x86 + x64 + 3.5 + {baeb9c48-562c-4d56-a6cd-18932265480a} + 2.0 + OpenNI-Windows-$(Platform)-2.2 + Package + $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets + $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets + Install + + + bin\$(Platform)\ + obj\$(Configuration)\ + RedistDir=$(SolutionDir)\Packaging\Output_$(Platform) + + + False + False + 1076 + ICE38;ICE43 + + + bin\$(Platform)\ + obj\$(Configuration)\ + RedistDir=$(SolutionDir)\Packaging\Output_$(Platform) + + + False + False + 1076 + ICE38;ICE43 + + + + + + + + + + + + + + + + + ClosestPointViewer + {bda3bf24-550a-4bf9-83e5-0006134eed40} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + EventBasedRead + {bda3bf24-5555-4bf9-83e5-7b56134edd40} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + MultiDepthViewer + {bda3bf24-550a-fff9-83e5-7b56134eee40} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + MultipleStreamRead + {920d08ac-452c-4326-bc6e-86fe65848587} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + MWClosestPointApp + {a0db36c9-ce6c-4f61-933c-e53a630d3c7e} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + MWClosestPoint + {4b01e59d-cc85-4b2e-b3a2-00f75856cb23} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + SimpleRead + {bda3bf24-550a-4bf9-83e5-7b56134edd40} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + SimpleViewer + {bda3bf24-550a-4bf9-83e5-7b56134eed40} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + OpenNI + {72d595bb-8c52-449b-91db-0e9f6aeaf53a} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + OniFile + {15ecc029-90de-4d1d-b00a-4a8e647d8c24} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + PS1080 + {9f6652af-35f2-452e-a2d3-08d05f5c075e} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + PSLink + {5b74f010-8b79-46b5-b906-c2b56cdb3386} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + NiViewer + {bda3bf24-550a-4bf9-83e5-0056134eed40} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + PSLinkConsole + {d39a4248-3985-41de-afd5-aec58d29291f} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + XnLib + {72d595bb-8c52-449b-91db-0e9f6aeaf5bb} + True + True + Binaries;Content;Satellites + INSTALLLOCATION + + + + + + + + + + + + + + + + + + $(WixExtDir)\WixUtilExtension.dll + WixUtilExtension + + + $(WixExtDir)\WixUIExtension.dll + WixUIExtension + + + + + + + + echo "Building Java Wrappers..." +call "$(SolutionDir)\Wrappers\java\OpenNI.java\Build.bat" $(PlatformName) +if errorlevel 1 exit 1 +echo "Building SimpleViewer.java..." +call "$(SolutionDir)\Samples\SimpleViewer.java\Build.bat" $(PlatformName) +if errorlevel 1 exit 1 +echo "Building Documentation..." +"$(SolutionDir)\Source\Documentation\Runme.py" +if errorlevel 1 exit 1 +echo "Harvesting Files..." +"$(ProjectDir)\..\Harvest.py" $(ProjectDir)\..\Output_$(PlatformName) $(PlatformName) +if errorlevel 1 exit 1 +echo "Creating File List..." +"$(WIX)\bin\heat" dir "$(ProjectDir)\..\Output_$(PlatformName)" -ag -scom -sfrag -srd -dr INSTALLLOCATION -cg RedistComponentGroup -var var.RedistDir -out "$(ProjectDir)\Fragments\RedistFragments.wxs" -sw5150 +if errorlevel 1 exit 1 + + + \ No newline at end of file diff --git a/Packaging/Install/Install.wxs b/Packaging/Install/Install.wxs new file mode 100644 index 0000000..a79f910 --- /dev/null +++ b/Packaging/Install/Install.wxs @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + = 501 AND ServicePackLevel >= 2) OR (VersionNT >= 502))]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Packaging/Install/Lang/en-us/Loc_en-us.wxl b/Packaging/Install/Lang/en-us/Loc_en-us.wxl new file mode 100644 index 0000000..7ad96b5 --- /dev/null +++ b/Packaging/Install/Lang/en-us/Loc_en-us.wxl @@ -0,0 +1,13 @@ + + + 1033 + OpenNI + SDK for Windows + 64-bit + + PrimeSense + This application is is not supported on your current OS. Minimal OS supported is Windows XP SP2 + .NET Framework 2.0 is required. Please install the .NET Framework then run this installer again. + A newer version of !(loc.ProductName) is already installed. + Choose the folder in which to install !(loc.ProductName) + \ No newline at end of file diff --git a/Packaging/Install/Resources/Microsoft_VC100_CRT_x64.msm b/Packaging/Install/Resources/Microsoft_VC100_CRT_x64.msm new file mode 100644 index 0000000000000000000000000000000000000000..374d90e9357b72327e9ecdf54effd9bec233b565 GIT binary patch literal 585728 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 x{LFxsbjXK)c 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�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 b_eq|$rGG%_i){f7C-~VP<$C`=QRsgF$sE@h literal 0 HcmV?d00001 diff --git a/Packaging/Install/Resources/Microsoft_VC100_CRT_x86.msm b/Packaging/Install/Resources/Microsoft_VC100_CRT_x86.msm new file mode 100644 index 0000000000000000000000000000000000000000..7a1c578c87fafa9d276a0589807baa9bd68364dd GIT binary patch literal 573440 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�P+S^E4Z{PZdlz0zn->faw!3;XD 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

;;o>@6g=+ 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 SOT0FBfU?d{ncn|T6#9QH9{B+P literal 0 HcmV?d00001 diff --git a/Packaging/Install/Resources/Microsoft_VC90_CRT_x86.msm b/Packaging/Install/Resources/Microsoft_VC90_CRT_x86.msm new file mode 100644 index 0000000000000000000000000000000000000000..49bb1c957c5c16b4df89182b4cf7dd1f5f73bbc5 GIT binary patch literal 607744 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

?d!OX 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

%=MK+h521`;G>0 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

X=d5hVLGc_B| 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�Q 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 literal 0 HcmV?d00001 diff --git a/Packaging/Install/Resources/Microsoft_VC90_CRT_x86_x64.msm b/Packaging/Install/Resources/Microsoft_VC90_CRT_x86_x64.msm new file mode 100644 index 0000000000000000000000000000000000000000..734399afcf460a72f42e5040004a1a831472841a GIT binary patch literal 637440 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 z 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

(pvZi<|<}$g( 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

#gAOA`L;HM+huz{gXB{j_u~ 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

_^HW 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�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�KJmf0o)`^ea0+fU3pVTj~OUqcwCtsBNer0RQ!l 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%0DuU91b_^H0)PsD27nHL0e}gB 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

J-1n=_gKr`z^Qdp zBlq%!FXQ%wE^T}xY%?rNCRNrqUBT1mCVOa#kQ!B;pNr5xRU?fnB*m?rJOp7kYU}=x zqy6yoviEV=H+lwMx?0?~6?xxRw& z^go{mpaOq7j>sb61vSc6m=X1YHEJ}S5aX&Tvi)nH)33+1eP>Dw^wg%vNiDPFrb$YR z6s1|}qHHyBuBvEfb?oEnLTBlV9rp9K*XmkJP#N2#n$X1$;E0#4fP(MdjU3@28{aYvweA5i`)g>3Wz`x6IUH40uC3& z63m1**b2%^1S$uz15bRQU6k(|Sqjf;Xf2F7I*+Ovw$s{&&QMA^F7DPkAA@~V(6DWX z+p}u}8!(~;tbe;wRzz-4QNioNL#Sj!BzCTYU?wANQE{$AHtgSwG~#Q$ZPxwJVGm{& z^xuqjS#^rl#$f1RR^D!Lq1=>itrcf+*@wru1cp5YOa|V2Y}OHj((KN<@mA-y=OK~V zpU3t#*`LIOVR}m$jyCdk`qrXgNrx2J+#yn8EQL3pWFudwe8E=}3$8WS0Cin@9fLG6 z4NaX@66!{=Pw47HyChO>bi$Xzy@S>G^gOuJx>^ULi5nw!vlHYB#QxM%%(S^yXO%kU zZ4}+Cs=b*3(fx6(KTIh6cM!|pT8Y{L;fhjPi$`5eyb=HL#2$)QKMHH!_Apm-?1dH6 zPO*E1+7SBBV{bK7B4YtLIWP0*n$l^8RW~-^RMK>D#JnEOb>=|??BqcD`!bbR*Jts1 zG~vix^br&W$(k$FKA@pF7;mdoTWSObMW%ouP%k1$I{x z4pDPU9BM<P64|5WVVg@mxB_+o6S|eS5MKQIA22s76jfH+sGgPVp?`Yu-qR z+=pj&@a_m`@$Mg+w`9O6orr71dn07_ugLx`NX!AVPm8G+|0Ayr7OlPN)V0OqN^vQ_ zXuO2p;0d@2YN-%f+P2&WrP_M3Ay_$4TJjjK$sg|$}Ou*Ln-{Q1Y%E$!cXlT44*9OFsW5f3zj z<1>vZLdl;DSxBVCN=->rFIhG%A3hA@N(s|u50n1Fx_ z8FJlA=_R;WWS4S>cvLdjkH_W-)v1iuUH*O%1h8 zb?kJDsb9^A!u8DxHJ1~?2cXk$oNUP9*@+r!HCv8G4*YjYrbcZ|gD)ynB+0AOjmg6j z#;Ipb8uZ{!qF-+*{Oxjfg4-%Sw<$6WpEa$o&-~huu3@`9OOQ-NS0NrBSOqEQE96|r z#;V;*8hN5xzY zc6s|m;*}{J^`O}iGpNN3w7y*w$OK0qu@$(nX%$A!gx5vBMJA2o8I9>e-o!p9 zg2Uk*KfW-RY08VI0n$6k+!NA2#v-VRQSq zct06K*LeN|`(6l1G+upsj9DTSVNG1R{RPweI0(B(fUkD1c7F^n_n7fV>Joio zMv8(!N8I^h1?BHcVaSy6h>jyDY(Qsxh|beksvqXfbS&l{YS@36Kg{Bqa@e4n9i?h3 z?WrkKVr*(;B`q!FOU5cS6~n4#l}ssv0f(}%vgmht6RL+5$y1nV8!|T5mDYyV38~YL zbMAB5Y*FuttJQyfN+o@iH0L{*t}>6+D*Q|lW7j&N?2t;c-POcGX6)oR%@)X`Q{R#4F;(4@x6&{tB@ z)zY=3sj5wmkTI8p_w#rD)4!~!t*s1TcAfXQ-w8 zB`#3Njk%wq!8A;eCNBAAniGC&WvF{t7EN`_?h$MiSsqbqVP1ZYQ0|Xisi3NPRvmw- zWcHDA5(77fNFHR~R#k3k@9Zvg4dk=&l@go5^2sP`47QX$rbWQ4Y*eqkPaaqFddmaJ zK}Qc6Ij+9Zyp7PjouppZ*Va1LY9H6v(bH9yKB_GLYg+_`Ayo&>cZOc+LuJ$bY0lz3 z6_r1)mO@r?Ses3d@NR0t9}Yi-HERmY@H?yv_o|8^03jAbKU%nWU>yp5xb2~a&f2luu z9CQtyHzim5&&A@wuecJ>^F;2Jg`YG{0a+C_W}5nBH$Rp@+uYj-8!R7I2fM0Iw8D1{ zTyS+bt<lT54x zlyk5EPI7r6&e-<_EUu7_Uxp3PC%ILQ|DVLZ(u4KHTXEy}H-u$e`wtXhpNV}mmC$BF zAv%F>!dufurmCg+VNdkIxo$c#0Dk6%-*o^bZ2?MQgL0wl+^X1eu)9 z5{O$OJ6T|JR%-K~k^#A_QP$>juGid-E|$jr?}rwv-Z~!rU!U2YS3o0rznsrC^w=7Z zLvQ`X3_uXXJd$P$sP!pmS&s3C4v+j$gv;h|I)V`I<_eTC z?bPNf5UO_I8U;7h5c03+dlas-NVYI_XYP+~PcfS$iZU7$drJpfkQ!>@O!aEI$p_SO z#88vk86=RD-1Cuo5N)9dA%Czzl2`G8m0Em zHLKU=q&uZu&suGAgxUu6tC-av!VYB#b_z$N@&QG}Fj$)>xT&Fh)x)hSNOb18x_xWl zQ<)l(oDn}UMF#KLjL_sUdF%}p73Uz{bafcsLM5%too3G9TdI0J88=7P2O5C|g%sOC z47ykrkoRhVOoq&=s;cvI7#jVib2izmyxk7zv!-QoYaHFTwfpCoy8m%)`P}wk<}>K> z;oaM%xpNKBY^sO8abSAK(2LQG=N$$(PYklOvipfv#UszSGfFF=wb9y*C6z z)P-o{_OgC31~aTPY*r>tP)mt!0$psH`mz#!2!>vkNJM>DvNnqR+2FYa&wq#Is>Z8^ zCPt(Ya|Np=N%AKDr%p0+-te#m5@$zrsS>YhmyS6>vHmCND1la1K(tX3p`?uUznX$Q zayMc~T*!byZw1V)>_W#cmc&-2(S8gvN?YB(5!bRLt@Ozv=Tz_`MVx2|-f9?6vTCW~ zfC7IX&M_Y6A0eN)EwZoY5Em7Y%M+QaMABw+IP!UAl1g1nr!SMhH@BugePxStgxEm} z-hZC5apJ>g5zXL}!irOIO*<)08t_WnreCMHnluk4*O|ZW3K!m!(}25RzW0vHS5?Ki znLfmBG#0ETI-asTb3axnEVqMWb4;XeLwJE|NHJhx9V|*Q9q2a1-UN*^Ikp-O8gOG` z<7S^lB)_K)nzJ1g}0fVDwuA!$Lsi(cO7vyofWt-WJ;G(vRRZ5Om< zhf4I>i`j5Gf^E!uC^xj7^Gu7qS69L~jkx-f!7y#KOW@)P8ikCE`+`yt&WMWKP;v>^ zqVzbq(q2hjb#6xvUT}@=5*-^wdPgE7#OP@R5yvj$WH@fWP0|hm@^#ruN9o-c6`3Xi zX(k>IQq(bfu>h}IpSR_j7{7id5Z9`uc?x@$KC#3MHU%!y{8W*JFO7a>srA`-waUfW zhV2Y!LpjlQk;gK#S7Nz|Brn0y!1o2Jzu#01=JyqcsAq8;nBL_!NPSWtEipc)qL4)uC)Q>O(Pa9WcAZz6|}ZzL8CshtopEtj!h@H<}!caJVpW|jdKM-LD7+gi=g^m<)pV9zhK*b_2w4-PxFl%)>e zMNEMz6-G+#1XdIbk`SND-C<7YP7H@cCb>*gmQ1DCNzr`KJXmdvqr6>AwWihP%UCp6 zbNr((CU{1?gPhZALz-ipLHAchwhp5v(Vy5$(K=W~(R9%?*kw%ero5k0wHBi)(Gu8U z*lmpJEFdQLNUJo5LZbwu)}vNoU)O*DlqVGfL*5Zh)9SMX&6)NfrWsB$vU?o8!jU(y zLnV;|7r@e6HnbZox08eg?%jnOVi5RRO~Nc?;SzSkjKnFcy1XIAP?%28uClR2DeXyW zqF6gvuaSV~{S)Q~2Ancyr}0$m7?-h9&CFfQ5yr|wnu&+&LXLrpF*{9X1<9s2W1W@4 zaRTNF5WET|9a^W=sv1k>hBRX&E{_=hLGelEb+GnbX!aLGtBgnFP&SZ}pJ1mh*{ngt zMlOOa3y7QH{#C28{CUNYjLS0`(QQHHtobSZslR!zUtme@W6x`huyYjq-OThV+!v4wC&4VjmtKWYxua3Xgn zSyI0*6YH2%8@Amp<1&vuvmkcUZ2?M9A&|x|B{91Bl?VC7*(f^_7$woVxR2S zyLD-NY`Cz5rHsz6o0F^tbXczgpCH{g?lPqjg@4WB%Gx*dw`_!Qu?Q7A_7c?gbdgyKeg`r&gO zQ0&0xVR*6=ikmLyhj9V-`6Bt|qbpR7!Xerzvt_duI*<$!vE<7LDy8=C= z>##a+65bDdbfw&PyyIpNF2iuO-0!+G1l@2wz+J0_hXi-i=FuGhmtIIq_(L%DHrNQ4 z;V?S@o8c;e%M56u9}T$Ng$}qJ;EFuNU^>9`A=nDIkp55sy69)lt~4PGDB~&z+G#If zMi!#5!BA!4)h`^)7`KQ zuEt?=5VpV!bO{sCfM$h7{jdqHL>Dp+Yw29I+J*>R3a~f>ZS*57OBM<*6P7L#UMDQ8 zl6%U_J^5s9x2*m&e$?w0G~BI_MloGj1tzS<40~V~irb7v=tFT^APyE*^H#BGTaSt+ z!A5nqg8>ezxdT#=Mf2>0Ubt=4#>Kjuurtv_jh*HI{h^$j(Rl&(g7LLs8HkNqPvy21 zAh(Ve@W$R^9E54YUjjhCNVjn*c6vJ4s0MhLIu1@bkBq%kh zKG4t}2}fqr{&l3iD>hrjqgzxLm!+fIR1?eSM*}n#Wu*MX{lNhJi2!{tKtB|q9}duu z1ZcTG-^}NE)k{}+`#6o8>Z9SMOBOZCIdh%iow08=?W8l%nU@=a$nVM-uH38%mrkMT zy=pS@t+*hlPtYlD)X5gIiZ6Lm9JHZiT=%ba24%{w&Zdr1?f|U~-LY_66JeyQt*Im2 zp!jX=k)~LE$5xMSh;5Ck4S1s4TU+a6P2zKX$petDH=Z_q_YaPm@?OGe<_}Nq8!YFK z1o(~nB>z}|pM6jAj|BK5?@IpR0RQw^$sad~z3zH#=ByiL-#BM(<-GX|7KRp8RWDw$blLJ1H?6#R)h#uv*VNY4uWe{- zYF^h8UcaHWEz%y{*wGp5+O&Dg)@|E&?7X$RM~~zBD%G1dclB8r+sWSM=JJL9fx)5O zd-mS`(L11f(L(I5)O|smldQhwm-8Oq|I|wx^;D`yJCCOYXNmWF?m5Tfk8x+&GwwW} z^7MC=J>$>w@zQGl-vG{0r|JLg7BH^#obSum0>+h|@#p!JTENfyd{Fu6nT}66RUUTU zMdot*>GLgbsoX)%INx#0@SS(veb2r7?%V&dkKg~mfd@Zv@S%qvIrPa#A3OZ`uYBr> zPk-jKM?UxD=bw7|nO{BnYrp;*&;I6b{r0gh{Lb$__j|v8{KOZZKl#EReCfrJFMs6^ zzxqdi{3oYg`qMvq`IT4y{Pfqp{*Ax*%WwYGYv20z>u-GLum7g_-M{_2H^2Ay|M1rL zfAEiQ|I-iOIrGmy`j>zG@xT4#?7RQ|)A#=4KY#XfPZ(oQ2DQCuvX@TVI!a)N zypSkT>29xD5H~s*-L&P+FmX=lI(>$%WzDRN62k-;-5$~`)9%ZW96cFwY->nM(Jn$x zxYJaAyI;4=Bo^K1WBuiID$7XaH7uvR*e3KZ*CK4|*{thiU6b}s!d30!Dwyk7n%v3v zPS%wk;@) zdm?RGNi8c^FKi}}zo@aKI`S3#ra>d2$yH3L5zuJ6p(C zIfXnzxxg^(=D7uNQtrD=8Ia3rD_ZDG=Ub(!lFM1Ib%+j>C2QuX`(QeiD_z7}#Xg*O zkFPRH%4!`(9=l}T!E(?|*1)wxEu^gv$FxiV%OA&T7FRWsVy~f@&hJXgK5E%%XF%SA z^{((GbSfI%LZ<7{SO>1SQ=G!8D!9f%>LVh@vYUZPCn1`r5-OABuzs?nFmmNyU8`w} z%}Z*3gu(lO^?saJ5)LXr+yd@P}fE^=@% zCk;aQUJqf2A!aa5PG}bwX$*{^iwzY6WtJ{@HH*85nz+lD5}z|ZnezWM;sD|hVnM{u z3LEr_@??-sh#Qg;DQBstE&k65+Er37!kl#cYzbKgEW}tjaUZf&#&g>=iRlx_&4}{k z&!K5~Zq&z`U{L5PEiB`%DqE+tijo_O*DUlZ(KVw=Y6~PCseb$LO}4S+KA5P@QtVPr z^`oqyDJxKoc=ub*U2b^~m83<)4M$m@j%XKYQQLI?(ebq$d0#Ut<1< zkxs5iyGVR5O7*dZj@T-#Y<$fa8AlER3GWUL4M@bu6#MlTW}dk8=0`Z#`x zA9wmPrTYqs&os6U5*6~=g6WFVV{1Yb=Y=>^mV!OnDhwK#I8K_q>ReIs%|tqH*yy~O zb~ztsQ|xJ&Ysy8(I$e^-^eB1EM}zChvUG97lFGA&A!^S6#|>rY65saGSRiw^QKEG&fTCr;wbgy$LcZJ#qMuWv z$h&AK@*B9SJ$?=cIvpF2=U|rjIAoeB<+yr$8gI&m8;?(j?=+|i@$R*t%TqEid4=C|c@Ysc5P13`MUKbVWelv(NkKx0r73;PNS2>N}!nS)OBxmiZrH zy0J-?f4`z-`mCa5`W{8g^wH=2<*#Ep9FyVIOh?)~WcdO#C^{m`^F_XA6O-jR&2&?z zlsBSi8GcOBQoqBBZkP3=Xjz^fMK?qF79J)S+`(eeueZz)=q z=d_}weMS^5?R`|yvV9y@w6xE#qGf$&6)n%RwkUd&Y+p=wZj$AxIpMFbl_&h|YuO2Z zd#Vo5l}vZBz8cf()^dHG@cNr9?=+9zChb=dh=2CDpFVTk>z}edicGg`koukW!neu# zeK|mn9QWscEI=P&I@%%afB3j(pAE9TGrg@|+G~*M4ctD{f$%MX@H(brv38$*z4BED z%Bvms+jCjKzEh@LJ!SH=6%(gageFdyIAz+jifb$AN37Z#qEL_Ti1ITcKL2B^b5`$* zVx{xb&HTNiPFx#x@OQ=`ZOE zko7Ir>gd4Ir`M;xBzQr6JUyt)mt4Q8_Kia|Yrb=T@9L+W1GRmx-g(P+Yq!53zD_5< z%cb70PYu`6p9G^n0Y-VG*l?aa&u)Qozo_S=1DT%rwVYvt?yg!T5o5qvwrru?{lB~?6c2x-M@QZ_rcc|TTtRq9cyqx>xUYEdBmg7V35h)ZB?CZ*N7 ze>iD4hZ2>!w8!$SAf?N3yzT+!K@r8#Z-QK9rIw{4YJ+WH!*{{Zea?%yp4IdwG8t{^Bv4)mvYSfK0-%_3e0b> z2zTwci+Q`9uz&kK%n#299~<7sjA|g{)~&?cUqg6Dy$bVb1>tR_2bh7^3E4JOlh-LA z%n>7F?OZNlh`>Y4m1%^DT(y|b;|P1_)RET-Cv^Nl*u3`@8TzzN&1ANhX^-3AT(xb#57SNl=UYZyF!@!o{(Fl33IO< z;ng(4C(j6LIi6yk-a=>|PWa(A;f^uFe2Hev7FWWqY{Gjl2*VdW!(5?Dm=rWwZJG(6&TS#mbT(}Z2kglRLnFlCkzmdFqa z>k@`I65b0Y97!N#zeKpGif~p3VfQHE`FXF%>j@K1C=x~(5q@(gG(SLiCY7+gknl$} zAzK&W%rV0LdEI0`g$Q?VB3xoXnCC*cDwObO0%7+BLV>%4IxU2D9|#?0zQ#1*B@~k& z98xFDvLIC2O;{O8D4#}{Tu9hcMYyb;kTOiTb#@QtmZgMJ5`?qW39Ggfdha5fbAT{5 ziICwUp>;Xoxu=93{e+B@gdCi`m~({*N970~>JT2WAyo7wY>6VYIZ62J5~2KE!kA{l zs(!+u2|||neVFqE34h8Ec4!h_Feh}{NyttkJRMKC`V3)m3E@}`p;|j(=m)~H3~w;Y z77*SOA}roWn6#PD*_=?wgYZc(p?fUh=PW{xD}+t=2t}Hw9`7T}7$Yoa>Bnr~nW89R z*(Sm?9YR+tLg8Ig>xJ}By)NNsCZT5mVQV>|)DyyeuLz5V312XhEGt+-Inm+w&!@)BWLhVi#{wh z-xPl6rJTIPYzfI+fy5^^q^)=h~Azrp^$IBG;o#VZn$+qf%y!W zAjM}%#E5c>=#Ks z?EpeWmw8TE*#k@7^+yqUyCF5usMjdU9yFvL_s!`Egg0l)ijLQK!FK73F)tf;gNC^D z3@1%nC^5dcG)_1aM0SM*EsYKZ{v)nCQgRat?SwV=X08vh4F~;I{mO%hHn0o!3a|JY0KU(L`B~B&!GWVQdrZs* zV)d!t-HT!&zWN9yHY)_!-5ibV-ur-AT3_$z0e6Ux@Vk6>*AY13DVQ34+yQO`805z0 zM}Umc({NK;cTgX8QRPmh!Lrr@c_Cgu@XwRC&V3aGPHvm>D<{TkN3s`!?nV>p?V_lV{L(4Y1L7PYAfsfJa7Q^TupnwPd)~W(pKJ*4e)|u zNl9a)vj?GFJ&0pg+BH8C_iAR1KGz2a#qItX{~ciQb|*#n2uADz-NwE^x(cMr>4D{#mk z4!$Gr3{>g!1`F=F!#RcgWBU!Pfje_psdY^Z(6Wvf^9}EStQ_^>*XHUF_OQIFu@c6nMP4~o4UN1j!NLTy<%ckkC|crrUJawzH`e2e%J;LUdc3V4#%i=5MdF13SO zyc=ymFP~G}z~&$f>90P}xx*ATKI&m|9XbpgJGZV?3EKtbefAuzTMR(Xv)%jDDSud- zH_qd9G9IeEmZs^h4giUOn=7>M>4W|Nzel9J6J)GA@TK8dBv^k+`KUXZ3U*9c2H!o7 zfmE8w0lvB;ApDficwgvoh>EVsx3SNJitG(X2CL#>V}Z`N?$blSF1ahLe#tSYsh1j& zK9dIPZl&w9e2Rk(^_t_s4Tr&dXUMb8zyvU>;;M|fun)3IPZh50w+F?Y>030I4}fa4 z>Z9Y^)4=GOXx8`ali-qlz}1462Gt++i=Oz{K~;h5;gvd(z>wqR)6n4tAy-3qj?tpv zR$G_)rvhKNyiPBml@K%csht#i)0xDp6Lrm=ciy)w~sNIx(>;VnS>r+}Q z62XgQfcfBi3s7X&ifzg8gCQv4S@P5aN|Y^Xw%kgC=c93@!*cO(KTSAHW$qs6E{f`C zT$luY2EvVJoA*P-KEchC^U}c3xungZ<`^WZc{Q7KM1o7olNBYG!hxG7u=3cQSV*-h z3;pPx4o-1G?M)6Rz$Z?3uv;z>g5GI*Z}Hp>)ThJLvssd$n6rrS=KDxc+_wBqe_1g2 z#0nMME=>T3i`EpSjwCp!b}34^`zXY38$4DyI|JlaOY=s)_Jy81ciU4E_JNDc(;Mr3 zJfU@UiO=Zu3`j~I$X;+I85VnU=|qPghiwc@_D$V!AlNVwz!DY@650@Roi`Qy)v}kA zJ_!T=&F_x{TG>L+nWFUAM1R<^@W*cDWl3;&o|Vn~mPCkK?5a9+EC{Z2e_wns^8nDa zTl3yqs6*-r8>@;1hv4;{1x3!v@vxVPSvvAU9DJbN8Zc6ifKqMqk6ZX{K)gPnisf=L z{d+n`S~MaKxYpdu7-))wA2(`4Ya=2d;dWQWwy|SiBRp16ILj2`AF{NxB%cIYjpYNu zcXpsZ)=!6^KAmkG{c&+ojEPX>lO&-Blh4nP}OEw=Er zHe_xsWlNS$fViAZkGo^cpn7ipJ^#I-kUen!v5iwWtjguoVB?IKx{8;BDrbyt&6FI~ z#FUhj8pLVUvDo9dEV6Q z*fgikvlL9{dm8d_slT}R+ez^fyVG@v&>dTKE6tJ<^=L_dh%a_eEjFB zi7Uv*b5aK0BOmv$tLjHSo*~Lwg?xO;*0wz4<8w?oZy+D1Cx1TV<38t$ej*=lk&cN$ zKF-*9wjcR8lf`3gUZSh*BobWLp~m7r<8?!+|Irw3Hf-@Mb=*A<28~(82-ZAMcBEF+)D?pAzYVd|d0% zwiM*!&vlj_MLteX{!5ULAMq=jKtBGeo^=ND@ydz~UyzSa=I+QuKJM|nwg&n5P)Cn4 z^6|WEg=XaAjC=MQARm9ieJlp~_*jv>HuCX?!bNV#$Db#3mLea2QN}kH`MB`XE%M06 z>B*l9`M6zGLlE+DX004mh&S?a{e|MH$j9?Pl{g_E7mFOMK|cO@)dF+myMLyoEqAH1eyk6qP3FPB)C-tO|kH?;|WkEhpPySrU z$JxA|F53IB^pB_^w$?Cy;s>AAcpg%@g_f z!l1HfAvk)06)K~D#J^3#~KK{6ly%_oUjLI+bkdN~fDy>C6PEY>JkdJRM z>Fz~7e*egWW5~y6MsSNFAOD<~QHp$gbEb(V@^R&No?Xbtn^$QcMLuq95nzFQJhc6W z0rK(1nmAkEdU#A4NXC zBq6R6`M8aWP#W@a?vq?m$j9qdIZhxS*Q#r7Kt68WrJ;>{oSytSk&ka$u~riKcr06Y zDe`fq!17|`PhANe>v`Ew#4Kc1B-f_!{(Q3WIN@nrw&7m<%|aiz*5 zAJ;j!xCHt5ECz;+$j1dgJgP)K{%*|B5BYfIT@O>_MM|s)06)qzwhKbtv@WQzAP7xa?{kETC3H>}-jG}iKJ3Inj zFMj}MJJ(-j=rb=X-E{ivm9AbTvi=#0vug#HM}^6qxWUi#^}oRv$Iw$-L*mQ)FUZCf ze-Ho_>pG1W%z3w`=W`essCXVg(%z2O_G*I?1A`5wkJpTK2YGVPRcV+NTKvMP2Lk2) zPoFpSex~0CWorLnYJVwH`#HUT_-6@e;e5(yIt5MfOwU&Y_p9T6S-X<4lY`zbiMw;6 z73lra?7cX@=S3AruGh!?@I1w2)SccBTAT9pE$RJGs!^w^X%Jub|NDJU?eFw{PVX0G zYJXsAT8QBpm|2%k|G@j>r~mWU zc`s3TeoE}(X7HA*a}jcZh8Op#+IHMv&Aun?T#FIR7Y$;DJ?3B& zUubYH&JAV+kDJ{Uod^ZW3FJLQ9Ih!t_|sd;&qWi>cE=$n0Bz;2|~uN$3B*hr2YsKbJSd{iTvP) z%DP0#(bbpxlr-z7ezsGF`fo0rE6;p{KYyG08S~Kcu2rl;cs;o-n-Fanj~F0|G;VZ7 zbcqt&kEs7nD+$qS@!89W)#+iCh|y|_FY$7&juFJTYYc2?l6~{V?ShEs>W1VH8wNh< zBSy=xIwP_OZVg2oZ_!A=kFyM1#O+tP_Yh0-ueBjcTwe7Fu^{vU3o1s5Yr4me=pt3K z5phUx92W^m++cG=q+Uu5LS)hOI*w@O=YAfsn)0~IwGVtojql%lrIw7-|Kl2e7 zrRZ&$z3e6UzME~8JNOhT2g`45zso@3imTUjG8dzGu}pOEy0TK_I@0dWS2m<5P893& z=4eo;mbBg9J4`7T*w|Y#uVzsAw?7K}{xh7iL8hwsxI#H)*`)CmwiAUEeYRDLb~ScT zvOm?VplVJ~hGk4fivm|s+jV)ycc?C-D&&gI`Vgy2{qp)zd&qJ}DsQl{8ke>g^?v%C zhu3obs5erDyz5#oQ?IpwYlqoms=fV1GsXukRDSsEplSPt%KgBzC|G*7ayRFf!sN9A z%5}1bIBvzSRBrc?X%FnTR9=*ya;dpmTiNn}n)7phE9HL^kM#Ex;O{-lH*R)39!?A5 z@nzPphsRg7CmCPA@O^vUH{|)0y;~%D};~UxsMqYIM z(7WI+K5htxSEteO5N8}SKHe#}Et{`FKdvyY9d^~HA5WB(w2Rep_9^iPTF9BukADtj z@x==(yDL5aZXc?hoTo(3yDoq7?Vo&WzivD{)7<1A^X-nXgr$x1RsJR4hF#aYkMeEi zt73bUZ}YWS-=KUevC(S=%D28gLry5)&Z>4^kMeB|Eyo?@TgQib+fY38OqB3N@$gv6 zv&SeNKHFT2oG=UE&N-=AhbjN;)|BB+_jDD9xC>~A@W=x`Z_;bVk>nI+UPZmm}cv!MS^BRhW)po4$C>~n6ySbux zxXkCv7>b9fvWc=N9-5R(YM^*HC^9$)#Y2zI!&WFBiaTl7p?DZ(>b(WU!!bUE^(Y>i zAGsxf;-TNX##8?q4~HoFqpjOe{TW}G4Yb?3MKn)yGZzfe3vB9OL+AzHCVHsp^F{E1H}K7Ew`Mvq64)NK-Ln>ni3jm(deX#slkgkJJTN(f@}^~`e+lw#M}M#1A{&juP##8MpheK{ z(e(Qv`u!;Vv19c63Hs-eOmCm0|D8c$pk~u~hThJj|D8`?hUx^b(H}!~f2gLfg8q0V zeL1R^drW5|z5SfN{005qMQ``g`Hp@^b&B*cN5kWUA=|~g~6*(@$+QFh70FKfqqwtiDpr>Wh+)-oTo2O?>DEZ--yFk zVqrR=+80j;r+@fmlXK;= z>mX?c38dapOqbL(O8g}Cj4>B*-6GB0fYc|}jNrON+E{Ge)OJXHA@_A$S6D8TgX;%r zmd?0Nuv~c!t^=fX_hV9Za&g^X`5{|e*GJ3e#q(FR+fT?mR&^w<(<^^2PwMf;-(k|E zQ%T+32~AvASH5xtlQx)!>*%OsYPe30raORHJ{F7X;ix~PNWGieQ%tIgFRp8µh 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 literal 0 HcmV?d00001 diff --git a/Packaging/Install/Resources/OpenNIBackground.bmp b/Packaging/Install/Resources/OpenNIBackground.bmp new file mode 100644 index 0000000000000000000000000000000000000000..98a00ecfe35902fa4df69d06f1b5bc3a84012d5e GIT binary patch literal 155830 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 JYtaDW{eP23IFSGV literal 0 HcmV?d00001 diff --git a/Packaging/Install/Resources/mainicon.ico b/Packaging/Install/Resources/mainicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..cb534a375b0729cd3ee088b2b71a1c7538656f91 GIT binary patch literal 10534 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 literal 0 HcmV?d00001 diff --git a/Packaging/Install/UI/CustomeInstallUI.wxs b/Packaging/Install/UI/CustomeInstallUI.wxs new file mode 100644 index 0000000..e644636 --- /dev/null +++ b/Packaging/Install/UI/CustomeInstallUI.wxs @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + 1 + + 1 + + 1 + 1 + + 1 + + 1 + 1 + 1 + + + + + NOT Installed + + + + + \ No newline at end of file diff --git a/Packaging/Install/setup.exe b/Packaging/Install/setup.exe new file mode 100644 index 0000000000000000000000000000000000000000..1f85b99ac658a35d91ceef323408145e5c138b87 GIT binary patch literal 126464 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 literal 0 HcmV?d00001 diff --git a/Packaging/Linux/install.sh b/Packaging/Linux/install.sh new file mode 100755 index 0000000..2243c9d --- /dev/null +++ b/Packaging/Linux/install.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +#/*************************************************************************** +#* * +#* OpenNI 2.0 * +#* Copyright (C) 2012 PrimeSense Ltd. * +#* * +#* This file is part of OpenNI2 installation. * +#* * +#* OpenNI is free software: you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License as published* +#* by the Free Software Foundation, either version 3 of the License, or * +#* (at your option) any later version. * +#* * +#* OpenNI is distributed in the hope that it will be useful, * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +#* GNU Lesser General Public License for more details. * +#* * +#* You should have received a copy of the GNU Lesser General Public License* +#* along with OpenNI. If not, see . * +#* * +#***************************************************************************/ +# + +# Check if user is root/running with sudo +if [ `whoami` != root ]; then + echo Please run this script with sudo + exit +fi + +ORIG_PATH=`pwd` +cd `dirname $0` +SCRIPT_PATH=`pwd` +cd $ORIG_PATH + +if [ "`uname -s`" != "Darwin" ]; then + # Install UDEV rules for USB device + cp ${SCRIPT_PATH}/primesense-usb.rules /etc/udev/rules.d/557-primesense-usb.rules +fi + +OUT_FILE="$SCRIPT_PATH/OpenNIDevEnvironment" + +echo "export OPENNI2_INCLUDE=$SCRIPT_PATH/Include" > $OUT_FILE +echo "export OPENNI2_REDIST=$SCRIPT_PATH/Redist" >> $OUT_FILE +chmod a+r $OUT_FILE diff --git a/Packaging/Linux/primesense-usb.rules b/Packaging/Linux/primesense-usb.rules new file mode 100644 index 0000000..8185a4c --- /dev/null +++ b/Packaging/Linux/primesense-usb.rules @@ -0,0 +1,14 @@ +# Make primesense device mount with writing permissions (default is read only for unknown devices) +SUBSYSTEM=="usb", ATTR{idProduct}=="0200", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="0300", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="0401", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="0500", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="0600", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="0601", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="0609", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="1250", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="1260", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="1270", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="1280", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="1290", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" +SUBSYSTEM=="usb", ATTR{idProduct}=="f9db", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video" \ No newline at end of file diff --git a/Packaging/ReleaseVersion.py b/Packaging/ReleaseVersion.py new file mode 100755 index 0000000..e2fdf5f --- /dev/null +++ b/Packaging/ReleaseVersion.py @@ -0,0 +1,185 @@ +#!/usr/bin/python + +#/**************************************************************************** +#* * +#* OpenNI 2.x Alpha * +#* Copyright (C) 2012 PrimeSense Ltd. * +#* * +#* This file is part of OpenNI. * +#* * +#* Licensed under the Apache License, Version 2.0 (the "License"); * +#* you may not use this file except in compliance with the License. * +#* You may obtain a copy of the License at * +#* * +#* http://www.apache.org/licenses/LICENSE-2.0 * +#* * +#* Unless required by applicable law or agreed to in writing, software * +#* distributed under the License is distributed on an "AS IS" BASIS, * +#* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +#* See the License for the specific language governing permissions and * +#* limitations under the License. * +#* * +#****************************************************************************/ +import os +import re +import sys +import shutil +import subprocess +import platform +import argparse +import stat + +import UpdateVersion + +if len(sys.argv) < 2 or sys.argv[1] in ('-h','--help'): + print "usage: " + sys.argv[0] + " [UpdateVersion]" + sys.exit(1) + +plat = sys.argv[1] +origDir = os.getcwd() + +shouldUpdate = 0 +if len(sys.argv) >= 3 and sys.argv[2] == 'UpdateVersion': + shouldUpdate = 1 + +if shouldUpdate == 1: + # Increase Build + UpdateVersion.VERSION_BUILD += 1 + UpdateVersion.update() + +def get_reg_values(reg_key, value_list): + # open the reg key + try: + reg_key = win32api.RegOpenKeyEx(*reg_key) + except pywintypes.error as e: + raise Exception("Failed to open registry key!") + # Get the values + try: + values = [(win32api.RegQueryValueEx(reg_key, name), data_type) for name, data_type in value_list] + # values list of ((value, type), expected_type) + for (value, data_type), expected in values: + if data_type != expected: + raise Exception("Bad registry value type! Expected %d, got %d instead." % (expected, data_type)) + # values okay, leave only values + values = [value for ((value, data_type), expected) in values] + except pywintypes.error as e: + raise Exception("Failed to get registry value!") + finally: + try: + win32api.RegCloseKey(reg_key) + except pywintypes.error as e: + # We don't care if reg key close failed... + pass + return tuple(values) + +def calc_jobs_number(): + cores = 1 + + try: + if isinstance(self, OSMac): + txt = gop('sysctl -n hw.physicalcpu') + else: + txt = gop('grep "processor\W:" /proc/cpuinfo | wc -l') + + cores = int(txt) + except: + pass + + return str(cores * 2) + +# Create installer +strVersion = UpdateVersion.getVersionName() +print "Creating installer for OpenNI " + strVersion + " " + plat +finalDir = "Final" +if not os.path.isdir(finalDir): + os.mkdir(finalDir) + +if plat == 'android': + if not 'NDK_ROOT' in os.environ: + print 'Please define NDK_ROOT!' + sys.exit(2) + + ndkDir = os.environ['NDK_ROOT'] + + buildDir = 'AndroidBuild' + if os.path.isdir(buildDir): + shutil.rmtree(buildDir) + + outputDir = 'OpenNI-android-' + strVersion + if os.path.isdir(outputDir): + shutil.rmtree(outputDir) + + os.makedirs(buildDir + '/jni') + os.symlink('../../../', buildDir + '/jni/OpenNI2') + shutil.copy('../Android.mk', buildDir + '/jni') + shutil.copy('../Application.mk', buildDir + '/jni') + rc = subprocess.call([ ndkDir + '/ndk-build', '-C', buildDir, '-j8' ]) + if rc != 0: + print 'Build failed!' + sys.exit(3) + + finalFile = finalDir + '/' + outputDir + '.tar' + + shutil.move(buildDir + '/libs/armeabi-v7a', outputDir) + + # add config files + shutil.copy('../Config/OpenNI.ini', outputDir) + shutil.copy('../Config/OpenNI2/Drivers/PS1080.ini', outputDir) + + print('Creating archive ' + finalFile) + subprocess.check_call(['tar', '-cf', finalFile, outputDir]) + +elif platform.system() == 'Windows': + import win32con,pywintypes,win32api,platform + + (bits,linkage) = platform.architecture() + matchObject = re.search('64',bits) + is_64_bit_machine = matchObject is not None + + if is_64_bit_machine: + MSVC_KEY = (win32con.HKEY_LOCAL_MACHINE, r"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0") + else: + MSVC_KEY = (win32con.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\VisualStudio\10.0") + + MSVC_VALUES = [("InstallDir", win32con.REG_SZ)] + VS_INST_DIR = get_reg_values(MSVC_KEY, MSVC_VALUES)[0] + PROJECT_SLN = "..\OpenNI.sln" + + bulidLog = origDir+'/build.Release.'+plat+'.txt' + devenv_cmd = '\"'+VS_INST_DIR + 'devenv\" '+PROJECT_SLN + ' /Project Install /Rebuild "Release|'+plat+'\" /out '+bulidLog + print(devenv_cmd) + subprocess.check_call(devenv_cmd, close_fds=True) + + # everything OK, can remove build log + os.remove(bulidLog) + + outFile = 'OpenNI-Windows-' + plat + '-' + strVersion + '.msi' + finalFile = os.path.join(finalDir, outFile) + if os.path.exists(finalFile): + os.remove(finalFile) + + shutil.move('Install/bin/' + plat + '/en-us/' + outFile, finalDir) + +elif platform.system() == 'Linux' or platform.system() == 'Darwin': + + devNull = open('/dev/null', 'w') + subprocess.check_call(['make', '-C', '../', '-j' + calc_jobs_number(), 'PLATFORM=' + plat, 'clean'], stdout=devNull, stderr=devNull) + devNull.close() + + buildLog = open(origDir + '/build.release.' + plat + '.log', 'w') + subprocess.check_call(['make', '-C', '../', '-j' + calc_jobs_number(), 'PLATFORM=' + plat, 'release'], stdout=buildLog, stderr=buildLog) + buildLog.close() + + # everything OK, can remove build log + os.remove(origDir + '/build.release.' + plat + '.log') + +else: + print "Unknown OS" + sys.exit(2) + +# also copy Release Notes and CHANGES documents +shutil.copy('../ReleaseNotes.txt', finalDir) +shutil.copy('../CHANGES.txt', finalDir) + +print "Installer can be found under: " + finalDir +print "Done" diff --git a/Packaging/UpdateVersion.py b/Packaging/UpdateVersion.py new file mode 100755 index 0000000..87358c4 --- /dev/null +++ b/Packaging/UpdateVersion.py @@ -0,0 +1,144 @@ +#/**************************************************************************** +#* * +#* OpenNI 2.x Alpha * +#* Copyright (C) 2012 PrimeSense Ltd. * +#* * +#* This file is part of OpenNI. * +#* * +#* Licensed under the Apache License, Version 2.0 (the "License"); * +#* you may not use this file except in compliance with the License. * +#* You may obtain a copy of the License at * +#* * +#* http://www.apache.org/licenses/LICENSE-2.0 * +#* * +#* Unless required by applicable law or agreed to in writing, software * +#* distributed under the License is distributed on an "AS IS" BASIS, * +#* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +#* See the License for the specific language governing permissions and * +#* limitations under the License. * +#* * +#****************************************************************************/ +# +import sys +import os +import re +import stat +from datetime import date + +VERSION_MAJOR = 2 +VERSION_MINOR = 2 +VERSION_MAINTENANCE = 0 +VERSION_BUILD = 33 + +def getVersionString(): + return str(VERSION_MAJOR) + "." + str(VERSION_MINOR) + "." + str(VERSION_MAINTENANCE) + "." + str(VERSION_BUILD) + +def getVersionName(): + if VERSION_MAINTENANCE != 0: + return str(VERSION_MAJOR) + "." + str(VERSION_MINOR) + "." + str(VERSION_MAINTENANCE) + elif VERSION_MINOR != 0: + return str(VERSION_MAJOR) + "." + str(VERSION_MINOR) + else: + return str(VERSION_MAJOR) + +def update(): + if VERSION_MAJOR > 9: + print( "Illegal major version") + sys.exit() + + if (VERSION_MINOR > 99): + print ("Illegal minor version") + sys.exit() + + if (VERSION_MAINTENANCE > 99): + print ("Illegal maintenance version") + sys.exit() + + if (VERSION_BUILD > 9999): + print ("Illegal build version") + sys.exit() + + print "Going to update files to version: " + getVersionString() + + update_self_defs("./UpdateVersion.py") + update_src_ver_defs("../Include/OniVersion.h") + update_wix_include("Install/Includes/Variables.wxi") + update_wix_project("Install/Install.wixproj") + update_doxygen("../Source/Documentation/Doxyfile") + update_release_notes("../ReleaseNotes.txt") + + print ("\n*** Done ***") + +def regx_replace(findStr,repStr,filePath): + "replaces all findStr by repStr in file filePath using regualr expression" + findStrRegx = re.compile(findStr) + tempName=filePath+'~~~' + fileMode = os.stat(filePath).st_mode + os.chmod(filePath, fileMode | stat.S_IWRITE) + input = open(filePath) + output = open(tempName,'w') + for s in input: + output.write(findStrRegx.sub(repStr,s)) + output.close() + input.close() + os.remove(filePath) + os.rename(tempName,filePath) + +def update_self_defs (filePath): + print(( "Updating self version defines: " + filePath)) + regx_replace("VERSION_MAJOR = \d+\n", "VERSION_MAJOR = " + str(VERSION_MAJOR) + "\n", filePath) + regx_replace("VERSION_MINOR = \d+\n", "VERSION_MINOR = " + str(VERSION_MINOR) + "\n", filePath) + regx_replace("VERSION_MAINTENANCE = \d+\n", "VERSION_MAINTENANCE = " + str(VERSION_MAINTENANCE) + "\n", filePath) + regx_replace("VERSION_BUILD = \d+\n", "VERSION_BUILD = " + str(VERSION_BUILD) + "\n", filePath) + +def update_src_ver_defs (filePath): + print(( "Updating source version defines: " + filePath)) + regx_replace("#define ONI_VERSION_MAJOR[ \t](.*)", "#define ONI_VERSION_MAJOR\t" + str(VERSION_MAJOR), filePath) + regx_replace("#define ONI_VERSION_MINOR[ \t](.*)", "#define ONI_VERSION_MINOR\t" + str(VERSION_MINOR), filePath) + regx_replace("#define ONI_VERSION_MAINTENANCE[ \t](.*)", "#define ONI_VERSION_MAINTENANCE\t" + str(VERSION_MAINTENANCE), filePath) + regx_replace("#define ONI_VERSION_BUILD[ \t](.*)", "#define ONI_VERSION_BUILD\t" + str(VERSION_BUILD), filePath) + +def update_wix_include (filePath): + print (("Updating wix include: " + filePath)) + regx_replace("define MajorVersion=(.*)", "define MajorVersion=" + str(VERSION_MAJOR) + "?>", filePath) + regx_replace("define MinorVersion=(.*)", "define MinorVersion=" + str(VERSION_MINOR) + "?>", filePath) + regx_replace("define MaintenanceVersion=(.*)", "define MaintenanceVersion=" + str(VERSION_MAINTENANCE) + "?>", filePath) + regx_replace("define BuildVersion=(.*)", "define BuildVersion=" + str(VERSION_BUILD) + "?>", filePath) + regx_replace("define VersionName=(.*)", "define VersionName=\"" + getVersionName() + "\"?>", filePath) + +def update_wix_project (filePath): + print (("Updating wix project: " + filePath)) + regx_replace("(.*)", "OpenNI-Windows-$(Platform)-" + getVersionName() + "", filePath) + +def update_doxygen (filePath): + print (("Updating doxygen: " + filePath)) + regx_replace("PROJECT_NAME\s*=\s*\"OpenNI (\d+)\.(\d+)\.(\d+)\"", "PROJECT_NAME = \"OpenNI " + getVersionName() + "\"", filePath) + +def update_release_notes (filePath): + print (("Updating release notes: " + filePath)) + + tempName = filePath + '~~~' + os.system("attrib -r " + filePath) + input = open(filePath) + output = open(tempName, 'w') + lines = input.readlines() + input.close() + today = date.today() + + lines[0] = 'OpenNI ' + str(VERSION_MAJOR) + '.' + str(VERSION_MINOR) + '.' + str(VERSION_MAINTENANCE) + ' Build ' + str(VERSION_BUILD) + '\n' + lines[1] = today.strftime('%B ') + str(today.day) + ' ' + str(today.year) + '\n' + + for s in lines: + output.write(s) + output.close() + os.remove(filePath) + os.rename(tempName,filePath) + +if __name__ == '__main__': + if len(sys.argv) == 5: + VERSION_MAJOR = int(sys.argv[1]) + VERSION_MINOR = int(sys.argv[2]) + VERSION_MAINTENANCE = int(sys.argv[3]) + VERSION_BUILD = int(sys.argv[4]) + + update() diff --git a/README b/README new file mode 100644 index 0000000..c91e93f --- /dev/null +++ b/README @@ -0,0 +1,94 @@ +OpenNI +------ + +Website: www.openni.org + +Buliding Prerequisites +====================== +Windows +------- +- Microsoft Visual Studio 2010 + From: http://msdn.microsoft.com/en-us/vstudio/bb984878.aspx +- Microsoft Kinect SDK v1.6 + From: http://go.microsoft.com/fwlink/?LinkID=262831 +- Python 2.6+/3.x + From: http://www.python.org/download/ +- PyWin32 + From: http://sourceforge.net/projects/pywin32/files/pywin32/ + Please make sure you download the version that matches your exact python version. +- JDK 6.0 + From: http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u32-downloads-1594644.html + You must also define an environment variable called "JAVA_HOME" that points to the JDK installation directory. + For example: set JAVA_HOME=c:\Program Files (x86)\Java\jdk1.6.0_32 +- WIX 3.5 + From: http://wix.codeplex.com/releases/view/60102 +- Doxygen + From: http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc +- GraphViz + From: http://www.graphviz.org/Download_windows.php + +Linux +----- +- GCC 4.x + From: http://gcc.gnu.org/releases.html + Or via apt: + sudo apt-get install g++ +- Python 2.6+/3.x + From: http://www.python.org/download/ + Or via apt: + sudo apt-get install python +- LibUSB 1.0.x + From: http://sourceforge.net/projects/libusb/files/libusb-1.0/ + Or via apt: + sudo apt-get install libusb-1.0-0-dev +- LibUDEV + sudo apt-get install libudev-dev +- JDK 6.0 + From: http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u32-downloads-1594644.html + Or via apt: + Ubuntu 10.x: + sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner" + sudo apt-get update + sudo apt-get install sun-java6-jdk + Ubuntu 12.x: + sudo apt-get install openjdk-6-jdk +- FreeGLUT3 + From: http://freeglut.sourceforge.net/index.php#download + Or via apt: + sudo apt-get install freeglut3-dev +- Doxygen + From: http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc + Or via apt: + sudo apt-get install doxygen +- GraphViz + From: http://www.graphviz.org/Download_linux_ubuntu.php + Or via apt: + sudo apt-get install graphviz + +Android +------- +- Android NDK r8d + From: http://developer.android.com/tools/sdk/ndk/index.html#Downloads + +Building +======== +Building on Windows: + Open the solution OpenNI.sln + +Building on Linux: + Run: + $ make + +Cross-Compiling for ARM on Linux: + The following environment variables should be defined: + - ARM_CXX= + - ARM_STAGING= + Then, run: + $ PLATFORM=Arm make + +Creating OpenNI2 package: + - Go into the directory 'Packaging' + - Run ReleaseVersion.py [x86|x64|arm|android] + NOTE: for android, NDK_ROOT must be defined, pointing to the NDK installation dir. + - Installer will be placed in the 'Final' directory + diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt new file mode 100644 index 0000000..c95a96e --- /dev/null +++ b/ReleaseNotes.txt @@ -0,0 +1,28 @@ +OpenNI 2.2.0 Build 33 +November 12 2013 + +Minimum Requirements: +--------------------- +- Operating Systems: + - Windows XP with SP2 and above, Windows 7, Windows 8, on x86 (32/64 bit) + - Ubuntu 12.04 (32/64/arm) and above + - Android 2.3 and above + - Mac OSX 10.7 and above +- Processors: + - Pentium 4, 1.4GHz and above + - AMD Athlon 64/FX 1GHz and above + - Arm Cortex A8 and above +- Memory: at least 64MB available. +- 250MB free hard disk space. +- Available USB 2.0 high-speed port. +- Development Environment: + - Microsoft Visual Studio 2008 and 2010. The compiler can be MSVC compiler or an Intel Compiler 11 and above. + - GCC 4.x +- Some of the sample applications require a graphics card equivalent to: ATI RADEON x1300 or NVIDIA GeForce 7300. + +Notes: +------ +- On Android, only native support (and samples) is currently provided. Please note that as bionic (Android linker) does not + support the rpath option, the samples cannot start as is. To solve this, do one of the following: + - Copy OpenNI libraries (libOpenNI2.so, libPS1080.so and libOniFile.so) to /system/lib (requires root) - or - + - run `export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH` before starting the native executeable diff --git a/Samples/Android.mk b/Samples/Android.mk new file mode 100644 index 0000000..1650a07 --- /dev/null +++ b/Samples/Android.mk @@ -0,0 +1,20 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifdef OPENNI2_ANDROID_OS_BUILD + $(info OpenNI2: Skipping samples in OS build...) +else + include $(call all-subdir-makefiles) +endif diff --git a/Samples/ClosestPointViewer/ClosestPointViewer.vcxproj b/Samples/ClosestPointViewer/ClosestPointViewer.vcxproj new file mode 100644 index 0000000..fe63dbf --- /dev/null +++ b/Samples/ClosestPointViewer/ClosestPointViewer.vcxproj @@ -0,0 +1,188 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {BDA3BF24-550A-4BF9-83E5-0006134EED40} + ClosestPointViewer + + + + Application + true + MultiByte + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\..\Include;..\..\ThirdParty\GL;..\;..\Common;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + glut32.lib;OpenNI2.lib;MWClosestPoint.lib + $(OutDir);..\..\ThirdParty\GL + true + + + ..\..\Include + + + + + Disabled + ..\..\Include;..\..\ThirdParty\GL;..\;..\Common;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + glut64.lib;OpenNI2.lib;MWClosestPoint.lib + $(OutDir);..\..\ThirdParty\GL + true + + + ..\..\Include + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\..\ThirdParty\GL;..\;..\Common;%(AdditionalIncludeDirectories) + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + glut32.lib;OpenNI2.lib;MWClosestPoint.lib + $(OutDir);..\..\ThirdParty\GL + true + + + ..\..\Include + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\..\ThirdParty\GL;..\;..\Common;%(AdditionalIncludeDirectories) + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + glut64.lib;OpenNI2.lib;MWClosestPoint.lib + $(OutDir);..\..\ThirdParty\GL + true + + + ..\..\Include + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/ClosestPointViewer/Makefile b/Samples/ClosestPointViewer/Makefile new file mode 100644 index 0000000..e9b7545 --- /dev/null +++ b/Samples/ClosestPointViewer/Makefile @@ -0,0 +1,27 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +INC_DIRS = \ + ../../Include \ + ../ \ + ../../ThirdParty/GL/ \ + ../Common + +SRC_FILES = *.cpp + +ifeq ("$(OSTYPE)","Darwin") + CFLAGS += -DMACOS + LDFLAGS += -framework OpenGL -framework GLUT +else + CFLAGS += -DUNIX -DGLX_GLXEXT_LEGACY + USED_LIBS += glut GL +endif + +USED_LIBS += OpenNI2 MWClosestPoint + +EXE_NAME = ClosestPointViewer + +CFLAGS += -Wall + +include ../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Samples/ClosestPointViewer/Viewer.cpp b/Samples/ClosestPointViewer/Viewer.cpp new file mode 100644 index 0000000..8c6e7e0 --- /dev/null +++ b/Samples/ClosestPointViewer/Viewer.cpp @@ -0,0 +1,266 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "Viewer.h" + +#if (ONI_PLATFORM == ONI_PLATFORM_MACOSX) + #include +#else + #include +#endif + +#include "OniSampleUtilities.h" + +#define GL_WIN_SIZE_X 1280 +#define GL_WIN_SIZE_Y 1024 +#define TEXTURE_SIZE 512 + +#define DEFAULT_DISPLAY_MODE DISPLAY_MODE_DEPTH + +#define MIN_NUM_CHUNKS(data_size, chunk_size) ((((data_size)-1) / (chunk_size) + 1)) +#define MIN_CHUNKS_SIZE(data_size, chunk_size) (MIN_NUM_CHUNKS(data_size, chunk_size) * (chunk_size)) + +SampleViewer* SampleViewer::ms_self = NULL; + +void SampleViewer::glutIdle() +{ + glutPostRedisplay(); +} +void SampleViewer::glutDisplay() +{ + SampleViewer::ms_self->display(); +} +void SampleViewer::glutKeyboard(unsigned char key, int x, int y) +{ + SampleViewer::ms_self->onKey(key, x, y); +} + +SampleViewer::SampleViewer(const char* strSampleName, const char* deviceUri) : + m_pClosestPoint(NULL), m_pClosestPointListener(NULL) + +{ + ms_self = this; + strncpy(m_strSampleName, strSampleName, ONI_MAX_STR); + + m_pClosestPoint = new closest_point::ClosestPoint(deviceUri); +} +SampleViewer::~SampleViewer() +{ + finalize(); + + delete[] m_pTexMap; + + ms_self = NULL; +} + +void SampleViewer::finalize() +{ + if (m_pClosestPoint != NULL) + { + m_pClosestPoint->resetListener(); + delete m_pClosestPoint; + m_pClosestPoint = NULL; + } + if (m_pClosestPointListener != NULL) + { + delete m_pClosestPointListener; + m_pClosestPointListener = NULL; + } +} + +openni::Status SampleViewer::init(int argc, char **argv) +{ + m_pTexMap = NULL; + + if (!m_pClosestPoint->isValid()) + { + return openni::STATUS_ERROR; + } + + m_pClosestPointListener = new MyMwListener; + m_pClosestPoint->setListener(*m_pClosestPointListener); + + return initOpenGL(argc, argv); + +} +openni::Status SampleViewer::run() //Does not return +{ + glutMainLoop(); + + return openni::STATUS_OK; +} +void SampleViewer::display() +{ + if (!m_pClosestPointListener->isAvailable()) + { + return; + } + + openni::VideoFrameRef depthFrame = m_pClosestPointListener->getFrame(); + const closest_point::IntPoint3D& closest = m_pClosestPointListener->getClosestPoint(); + m_pClosestPointListener->setUnavailable(); + + if (m_pTexMap == NULL) + { + // Texture map init + m_nTexMapX = MIN_CHUNKS_SIZE(depthFrame.getWidth(), TEXTURE_SIZE); + m_nTexMapY = MIN_CHUNKS_SIZE(depthFrame.getHeight(), TEXTURE_SIZE); + m_pTexMap = new openni::RGB888Pixel[m_nTexMapX * m_nTexMapY]; + } + + + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, GL_WIN_SIZE_X, GL_WIN_SIZE_Y, 0, -1.0, 1.0); + + if (depthFrame.isValid()) + { + calculateHistogram(m_pDepthHist, MAX_DEPTH, depthFrame); + } + + memset(m_pTexMap, 0, m_nTexMapX*m_nTexMapY*sizeof(openni::RGB888Pixel)); + + float factor[3] = {1, 1, 1}; + // check if we need to draw depth frame to texture + if (depthFrame.isValid()) + { + const openni::DepthPixel* pDepthRow = (const openni::DepthPixel*)depthFrame.getData(); + openni::RGB888Pixel* pTexRow = m_pTexMap + depthFrame.getCropOriginY() * m_nTexMapX; + int rowSize = depthFrame.getStrideInBytes() / sizeof(openni::DepthPixel); + int width = depthFrame.getWidth(); + int height = depthFrame.getHeight(); + + for (int y = 0; y < height; ++y) + { + const openni::DepthPixel* pDepth = pDepthRow; + openni::RGB888Pixel* pTex = pTexRow + depthFrame.getCropOriginX(); + + for (int x = 0; x < width; ++x, ++pDepth, ++pTex) + { + if (*pDepth != 0) + { + if (*pDepth == closest.Z) + { + factor[0] = factor[1] = 0; + } +// // Add debug lines - every 10cm +// else if ((*pDepth / 10) % 10 == 0) +// { +// factor[0] = factor[2] = 0; +// } + + int nHistValue = m_pDepthHist[*pDepth]; + pTex->r = nHistValue*factor[0]; + pTex->g = nHistValue*factor[1]; + pTex->b = nHistValue*factor[2]; + + factor[0] = factor[1] = factor[2] = 1; + } + } + + pDepthRow += rowSize; + pTexRow += m_nTexMapX; + } + } + + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_nTexMapX, m_nTexMapY, 0, GL_RGB, GL_UNSIGNED_BYTE, m_pTexMap); + + // Display the OpenGL texture map + glColor4f(1,1,1,1); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + + int nXRes = depthFrame.getWidth(); + int nYRes = depthFrame.getHeight(); + + // upper left + glTexCoord2f(0, 0); + glVertex2f(0, 0); + // upper right + glTexCoord2f((float)nXRes/(float)m_nTexMapX, 0); + glVertex2f(GL_WIN_SIZE_X, 0); + // bottom right + glTexCoord2f((float)nXRes/(float)m_nTexMapX, (float)nYRes/(float)m_nTexMapY); + glVertex2f(GL_WIN_SIZE_X, GL_WIN_SIZE_Y); + // bottom left + glTexCoord2f(0, (float)nYRes/(float)m_nTexMapY); + glVertex2f(0, GL_WIN_SIZE_Y); + + glEnd(); + glDisable(GL_TEXTURE_2D); + + float closestCoordinates[3] = {closest.X*GL_WIN_SIZE_X/float(depthFrame.getWidth()), closest.Y*GL_WIN_SIZE_Y/float(depthFrame.getHeight()), 0}; + + glVertexPointer(3, GL_FLOAT, 0, closestCoordinates); + glColor3f(1.f, 0.f, 0.f); + glPointSize(10); + glDrawArrays(GL_POINTS, 0, 1); + glFlush(); + + // Swap the OpenGL display buffers + glutSwapBuffers(); + +} + +void SampleViewer::onKey(unsigned char key, int /*x*/, int /*y*/) +{ + switch (key) + { + case 27: + finalize(); + exit (1); + } + +} + +openni::Status SampleViewer::initOpenGL(int argc, char **argv) +{ + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); + glutInitWindowSize(GL_WIN_SIZE_X, GL_WIN_SIZE_Y); + glutCreateWindow (m_strSampleName); + // glutFullScreen(); + glutSetCursor(GLUT_CURSOR_NONE); + + initOpenGLHooks(); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + + glEnableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + return openni::STATUS_OK; + +} +void SampleViewer::initOpenGLHooks() +{ + glutKeyboardFunc(glutKeyboard); + glutDisplayFunc(glutDisplay); + glutIdleFunc(glutIdle); +} diff --git a/Samples/ClosestPointViewer/Viewer.h b/Samples/ClosestPointViewer/Viewer.h new file mode 100644 index 0000000..1208950 --- /dev/null +++ b/Samples/ClosestPointViewer/Viewer.h @@ -0,0 +1,101 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_SAMPLE_VIEWER_H_ +#define _ONI_SAMPLE_VIEWER_H_ + +#include "MWClosestPoint/MWClosestPoint.h" +#include + +#define MAX_DEPTH 10000 + +class MyMwListener : public closest_point::ClosestPoint::Listener +{ +public: + MyMwListener() : m_ready(false) {} + virtual ~MyMwListener() {} + void readyForNextData(closest_point::ClosestPoint* pClosestPoint) + { + openni::Status rc = pClosestPoint->getNextData(m_closest, m_frame); + + if (rc == openni::STATUS_OK) + { +// printf("%d, %d, %d\n", m_closest.X, m_closest.Y, m_closest.Z); + } + else + { + printf("Update failed\n"); + } + m_ready = true; + } + + const openni::VideoFrameRef& getFrame() {return m_frame;} + const closest_point::IntPoint3D& getClosestPoint() {return m_closest;} + bool isAvailable() const {return m_ready;} + void setUnavailable() {m_ready = false;} +private: + openni::VideoFrameRef m_frame; + closest_point::IntPoint3D m_closest; + bool m_ready; +}; + + +class SampleViewer +{ +public: + SampleViewer(const char* strSampleName, const char* deviceUri); + virtual ~SampleViewer(); + + virtual openni::Status init(int argc, char **argv); + virtual openni::Status run(); //Does not return + +protected: + virtual void display(); + virtual void displayPostDraw(){}; // Overload to draw over the screen image + + virtual void onKey(unsigned char key, int x, int y); + + virtual openni::Status initOpenGL(int argc, char **argv); + void initOpenGLHooks(); + + void finalize(); + +private: + SampleViewer(const SampleViewer&); + SampleViewer& operator=(SampleViewer&); + + static SampleViewer* ms_self; + static void glutIdle(); + static void glutDisplay(); + static void glutKeyboard(unsigned char key, int x, int y); + + float m_pDepthHist[MAX_DEPTH]; + char m_strSampleName[ONI_MAX_STR]; + openni::RGB888Pixel*m_pTexMap; + unsigned int m_nTexMapX; + unsigned int m_nTexMapY; + + closest_point::ClosestPoint* m_pClosestPoint; + MyMwListener* m_pClosestPointListener; + +}; + + +#endif // _ONI_SAMPLE_VIEWER_H_ diff --git a/Samples/ClosestPointViewer/main.cpp b/Samples/ClosestPointViewer/main.cpp new file mode 100644 index 0000000..77da1e7 --- /dev/null +++ b/Samples/ClosestPointViewer/main.cpp @@ -0,0 +1,41 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "Viewer.h" + +int main(int argc, char** argv) +{ + openni::Status rc = openni::STATUS_OK; + + const char* deviceURI = openni::ANY_DEVICE; + if (argc > 1) + { + deviceURI = argv[1]; + } + + SampleViewer sampleViewer("ClosestPoint Viewer", deviceURI); + + rc = sampleViewer.init(argc, argv); + if (rc != openni::STATUS_OK) + { + return 1; + } + sampleViewer.run(); +} \ No newline at end of file diff --git a/Samples/Common/OniSampleUtilities.h b/Samples/Common/OniSampleUtilities.h new file mode 100644 index 0000000..a036d9f --- /dev/null +++ b/Samples/Common/OniSampleUtilities.h @@ -0,0 +1,119 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_SAMPLE_UTILITIES_H_ +#define _ONI_SAMPLE_UTILITIES_H_ + +#include +#include + +#ifdef WIN32 +#include +int wasKeyboardHit() +{ + return (int)_kbhit(); +} + +#else // linux + +#include +#include +#include +#include +int wasKeyboardHit() +{ + struct termios oldt, newt; + int ch; + int oldf; + + // don't echo and don't wait for ENTER + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + + // make it non-blocking (so we can check without waiting) + if (0 != fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK)) + { + return 0; + } + + ch = getchar(); + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + if (0 != fcntl(STDIN_FILENO, F_SETFL, oldf)) + { + return 0; + } + + if(ch != EOF) + { + ungetc(ch, stdin); + return 1; + } + + return 0; +} + +void Sleep(int millisecs) +{ + usleep(millisecs * 1000); +} +#endif // WIN32 + +void calculateHistogram(float* pHistogram, int histogramSize, const openni::VideoFrameRef& frame) +{ + const openni::DepthPixel* pDepth = (const openni::DepthPixel*)frame.getData(); + // Calculate the accumulative histogram (the yellow display...) + memset(pHistogram, 0, histogramSize*sizeof(float)); + int restOfRow = frame.getStrideInBytes() / sizeof(openni::DepthPixel) - frame.getWidth(); + int height = frame.getHeight(); + int width = frame.getWidth(); + + unsigned int nNumberOfPoints = 0; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x, ++pDepth) + { + if (*pDepth != 0) + { + pHistogram[*pDepth]++; + nNumberOfPoints++; + } + } + pDepth += restOfRow; + } + for (int nIndex=1; nIndex + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {BDA3BF24-5555-4BF9-83E5-7B56134EDD40} + EventBasedRead + + + + Application + true + MultiByte + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + OpenNI2.lib + $(OutDir) + true + + + + + Disabled + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + OpenNI2.lib + $(OutDir) + true + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + OpenNI2.lib + $(OutDir) + true + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + OpenNI2.lib + $(OutDir) + true + + + + + + + + + \ No newline at end of file diff --git a/Samples/EventBasedRead/Makefile b/Samples/EventBasedRead/Makefile new file mode 100644 index 0000000..280ed66 --- /dev/null +++ b/Samples/EventBasedRead/Makefile @@ -0,0 +1,17 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +INC_DIRS = \ + ../../Include \ + ../Common + +SRC_FILES = *.cpp + +USED_LIBS += OpenNI2 + +EXE_NAME = EventBasedRead + +CFLAGS += -Wall + +include ../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Samples/EventBasedRead/main.cpp b/Samples/EventBasedRead/main.cpp new file mode 100644 index 0000000..be4c130 --- /dev/null +++ b/Samples/EventBasedRead/main.cpp @@ -0,0 +1,156 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include +#include "OpenNI.h" + +#include "OniSampleUtilities.h" + +using namespace openni; + +void analyzeFrame(const VideoFrameRef& frame) +{ + DepthPixel* pDepth; + RGB888Pixel* pColor; + + int middleIndex = (frame.getHeight()+1)*frame.getWidth()/2; + + switch (frame.getVideoMode().getPixelFormat()) + { + case PIXEL_FORMAT_DEPTH_1_MM: + case PIXEL_FORMAT_DEPTH_100_UM: + pDepth = (DepthPixel*)frame.getData(); + printf("[%08llu] %8d\n", (long long)frame.getTimestamp(), + pDepth[middleIndex]); + break; + case PIXEL_FORMAT_RGB888: + pColor = (RGB888Pixel*)frame.getData(); + printf("[%08llu] 0x%02x%02x%02x\n", (long long)frame.getTimestamp(), + pColor[middleIndex].r&0xff, + pColor[middleIndex].g&0xff, + pColor[middleIndex].b&0xff); + break; + default: + printf("Unknown format\n"); + } +} + +class PrintCallback : public VideoStream::NewFrameListener +{ +public: + void onNewFrame(VideoStream& stream) + { + stream.readFrame(&m_frame); + + analyzeFrame(m_frame); + } +private: + VideoFrameRef m_frame; +}; + +class OpenNIDeviceListener : public OpenNI::DeviceConnectedListener, + public OpenNI::DeviceDisconnectedListener, + public OpenNI::DeviceStateChangedListener +{ +public: + virtual void onDeviceStateChanged(const DeviceInfo* pInfo, DeviceState state) + { + printf("Device \"%s\" error state changed to %d\n", pInfo->getUri(), state); + } + + virtual void onDeviceConnected(const DeviceInfo* pInfo) + { + printf("Device \"%s\" connected\n", pInfo->getUri()); + } + + virtual void onDeviceDisconnected(const DeviceInfo* pInfo) + { + printf("Device \"%s\" disconnected\n", pInfo->getUri()); + } +}; + +int main() +{ + Status rc = OpenNI::initialize(); + if (rc != STATUS_OK) + { + printf("Initialize failed\n%s\n", OpenNI::getExtendedError()); + return 1; + } + + OpenNIDeviceListener devicePrinter; + + OpenNI::addDeviceConnectedListener(&devicePrinter); + OpenNI::addDeviceDisconnectedListener(&devicePrinter); + OpenNI::addDeviceStateChangedListener(&devicePrinter); + + openni::Array deviceList; + openni::OpenNI::enumerateDevices(&deviceList); + for (int i = 0; i < deviceList.getSize(); ++i) + { + printf("Device \"%s\" already connected\n", deviceList[i].getUri()); + } + + Device device; + rc = device.open(ANY_DEVICE); + if (rc != STATUS_OK) + { + printf("Couldn't open device\n%s\n", OpenNI::getExtendedError()); + return 2; + } + + VideoStream depth; + + if (device.getSensorInfo(SENSOR_DEPTH) != NULL) + { + rc = depth.create(device, SENSOR_DEPTH); + if (rc != STATUS_OK) + { + printf("Couldn't create depth stream\n%s\n", OpenNI::getExtendedError()); + } + } + rc = depth.start(); + if (rc != STATUS_OK) + { + printf("Couldn't start the depth stream\n%s\n", OpenNI::getExtendedError()); + } + + + PrintCallback depthPrinter; + + // Register to new frame + depth.addNewFrameListener(&depthPrinter); + + // Wait while we're getting frames through the printer + while (!wasKeyboardHit()) + { + Sleep(100); + } + + depth.removeNewFrameListener(&depthPrinter); + + + depth.stop(); + depth.destroy(); + device.close(); + OpenNI::shutdown(); + + return 0; +} diff --git a/Samples/MWClosestPoint/MWClosestPoint.cpp b/Samples/MWClosestPoint/MWClosestPoint.cpp new file mode 100644 index 0000000..75a8ad0 --- /dev/null +++ b/Samples/MWClosestPoint/MWClosestPoint.cpp @@ -0,0 +1,215 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include +#include "MWClosestPoint.h" + +using namespace openni; + +namespace closest_point +{ + +class StreamListener; + +struct ClosestPointInternal +{ + ClosestPointInternal(ClosestPoint* pClosestPoint) : + m_pDevice(NULL), m_pDepthStream(NULL), m_pListener(NULL), m_pStreamListener(NULL), m_pClosesPoint(pClosestPoint) + {} + + void Raise() + { + if (m_pListener != NULL) + m_pListener->readyForNextData(m_pClosesPoint); + } + bool m_oniOwner; + Device* m_pDevice; + VideoStream* m_pDepthStream; + + ClosestPoint::Listener* m_pListener; + + StreamListener* m_pStreamListener; + + ClosestPoint* m_pClosesPoint; +}; + +class StreamListener : public VideoStream::NewFrameListener +{ +public: + StreamListener(ClosestPointInternal* pClosestPoint) : m_pClosestPoint(pClosestPoint) + {} + virtual void onNewFrame(VideoStream& stream) + { + m_pClosestPoint->Raise(); + } +private: + ClosestPointInternal* m_pClosestPoint; +}; + +ClosestPoint::ClosestPoint(const char* uri) +{ + m_pInternal = new ClosestPointInternal(this); + + m_pInternal->m_pDevice = new Device; + m_pInternal->m_oniOwner = true; + + OpenNI::initialize(); + Status rc = m_pInternal->m_pDevice->open(uri); + if (rc != STATUS_OK) + { + printf("Open device failed:\n%s\n", OpenNI::getExtendedError()); + return; + } + initialize(); +} + +ClosestPoint::ClosestPoint(openni::Device* pDevice) +{ + m_pInternal = new ClosestPointInternal(this); + + m_pInternal->m_pDevice = pDevice; + m_pInternal->m_oniOwner = false; + + OpenNI::initialize(); + + if (pDevice != NULL) + { + initialize(); + } +} + +void ClosestPoint::initialize() +{ + m_pInternal->m_pStreamListener = NULL; + m_pInternal->m_pListener = NULL; + + m_pInternal->m_pDepthStream = new VideoStream; + Status rc = m_pInternal->m_pDepthStream->create(*m_pInternal->m_pDevice, SENSOR_DEPTH); + if (rc != STATUS_OK) + { + printf("Created failed\n%s\n", OpenNI::getExtendedError()); + return; + } + + m_pInternal->m_pStreamListener = new StreamListener(m_pInternal); + + rc = m_pInternal->m_pDepthStream->start(); + if (rc != STATUS_OK) + { + printf("Start failed:\n%s\n", OpenNI::getExtendedError()); + } + + m_pInternal->m_pDepthStream->addNewFrameListener(m_pInternal->m_pStreamListener); +} + +ClosestPoint::~ClosestPoint() +{ + if (m_pInternal->m_pDepthStream != NULL) + { + m_pInternal->m_pDepthStream->removeNewFrameListener(m_pInternal->m_pStreamListener); + + m_pInternal->m_pDepthStream->stop(); + m_pInternal->m_pDepthStream->destroy(); + + delete m_pInternal->m_pDepthStream; + } + + if (m_pInternal->m_pStreamListener != NULL) + { + delete m_pInternal->m_pStreamListener; + } + + if (m_pInternal->m_oniOwner) + { + if (m_pInternal->m_pDevice != NULL) + { + m_pInternal->m_pDevice->close(); + + delete m_pInternal->m_pDevice; + } + } + + OpenNI::shutdown(); + + + delete m_pInternal; +} + +bool ClosestPoint::isValid() const +{ + if (m_pInternal == NULL) + return false; + if (m_pInternal->m_pDevice == NULL) + return false; + if (m_pInternal->m_pDepthStream == NULL) + return false; + if (!m_pInternal->m_pDepthStream->isValid()) + return false; + + return true; +} + +Status ClosestPoint::setListener(Listener& listener) +{ + m_pInternal->m_pListener = &listener; + return STATUS_OK; +} +void ClosestPoint::resetListener() +{ + m_pInternal->m_pListener = NULL; +} + +Status ClosestPoint::getNextData(IntPoint3D& closestPoint, VideoFrameRef& rawFrame) +{ + Status rc = m_pInternal->m_pDepthStream->readFrame(&rawFrame); + if (rc != STATUS_OK) + { + printf("readFrame failed\n%s\n", OpenNI::getExtendedError()); + } + + DepthPixel* pDepth = (DepthPixel*)rawFrame.getData(); + bool found = false; + closestPoint.Z = 0xffff; + int width = rawFrame.getWidth(); + int height = rawFrame.getHeight(); + + for (int y = 0; y < height; ++y) + for (int x = 0; x < width; ++x, ++pDepth) + { + if (*pDepth < closestPoint.Z && *pDepth != 0) + { + closestPoint.X = x; + closestPoint.Y = y; + closestPoint.Z = *pDepth; + found = true; + } + } + + if (!found) + { + return STATUS_ERROR; + } + + return STATUS_OK; +} + +} + + diff --git a/Samples/MWClosestPoint/MWClosestPoint.h b/Samples/MWClosestPoint/MWClosestPoint.h new file mode 100644 index 0000000..e51def1 --- /dev/null +++ b/Samples/MWClosestPoint/MWClosestPoint.h @@ -0,0 +1,82 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _MW_CLOSEST_POINT_H_ +#define _MW_CLOSEST_POINT_H_ + +#include + +#ifdef _CLOSEST_POINT +#define MW_CP_API ONI_API_EXPORT +#else +#define MW_CP_API ONI_API_IMPORT +#endif + + +namespace openni +{ + class Device; +} + +namespace closest_point +{ + +struct IntPoint3D +{ + int X; + int Y; + int Z; +}; + +struct ClosestPointInternal; + +class MW_CP_API ClosestPoint +{ +public: + class Listener + { + public: + virtual void readyForNextData(ClosestPoint*) = 0; + }; + + ClosestPoint(const char* uri = NULL); + ClosestPoint(openni::Device* pDevice); + ~ClosestPoint(); + + bool isValid() const; + + openni::Status setListener(Listener& listener); + void resetListener(); + + openni::Status getNextData(IntPoint3D& closestPoint, openni::VideoFrameRef& rawFrame); +private: + void initialize(); + + ClosestPointInternal* m_pInternal; +}; + +} + + + + + + +#endif // _MW_CLOSEST_POINT_H_ diff --git a/Samples/MWClosestPoint/MWClosestPoint.vcxproj b/Samples/MWClosestPoint/MWClosestPoint.vcxproj new file mode 100644 index 0000000..74cafe7 --- /dev/null +++ b/Samples/MWClosestPoint/MWClosestPoint.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4B01E59D-CC85-4B2E-B3A2-00F75856CB23} + MWClosestPoint + + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Level3 + Disabled + ..\..\Include + _CLOSEST_POINT;%(PreprocessorDefinitions) + true + true + + + + + true + $(OutDir) + OpenNI2.lib;%(AdditionalDependencies) + true + + + ..\..\Include + + + + + Level3 + Disabled + ..\..\Include + _CLOSEST_POINT;%(PreprocessorDefinitions) + true + true + + + + + true + $(OutDir) + OpenNI2.lib;%(AdditionalDependencies) + true + + + ..\..\Include + + + + + Level3 + MaxSpeed + true + ..\..\Include + _CLOSEST_POINT;%(PreprocessorDefinitions) + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + $(OutDir) + OpenNI2.lib;%(AdditionalDependencies) + true + + + ..\..\Include + + + + + Level3 + MaxSpeed + true + ..\..\Include + _CLOSEST_POINT;%(PreprocessorDefinitions) + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + $(OutDir) + OpenNI2.lib;%(AdditionalDependencies) + true + + + ..\..\Include + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/MWClosestPoint/MWClosestPoint.vcxproj.filters b/Samples/MWClosestPoint/MWClosestPoint.vcxproj.filters new file mode 100644 index 0000000..8d0d9ab --- /dev/null +++ b/Samples/MWClosestPoint/MWClosestPoint.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Samples/MWClosestPoint/Makefile b/Samples/MWClosestPoint/Makefile new file mode 100644 index 0000000..6ca4977 --- /dev/null +++ b/Samples/MWClosestPoint/Makefile @@ -0,0 +1,16 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +INC_DIRS = \ + ../../Include + +SRC_FILES = *.cpp + +USED_LIBS += OpenNI2 + +LIB_NAME = MWClosestPoint + +CFLAGS += -Wall -D_CLOSEST_POINT + +include ../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Samples/MWClosestPointApp/MWClosestPointApp.vcxproj b/Samples/MWClosestPointApp/MWClosestPointApp.vcxproj new file mode 100644 index 0000000..063ec37 --- /dev/null +++ b/Samples/MWClosestPointApp/MWClosestPointApp.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {A0DB36C9-CE6C-4F61-933C-E53A630D3C7E} + MWClosestPointApp + + + + Application + true + MultiByte + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Level3 + Disabled + ..\MWClosestPoint;..\..\Include + true + true + + + + + true + $(OutDir) + MWClosestPoint.lib;OpenNI2.lib;%(AdditionalDependencies) + true + + + ..\..\Include + + + + + Level3 + Disabled + ..\MWClosestPoint;..\..\Include + true + true + + + + + true + $(OutDir) + MWClosestPoint.lib;OpenNI2.lib;%(AdditionalDependencies) + true + + + ..\..\Include + + + + + Level3 + MaxSpeed + true + ..\MWClosestPoint;..\..\Include + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + MWClosestPoint.lib;OpenNI2.lib;%(AdditionalDependencies) + $(OutDir) + true + + + ..\..\Include + + + + + Level3 + MaxSpeed + true + ..\MWClosestPoint;..\..\Include + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + MWClosestPoint.lib;OpenNI2.lib;%(AdditionalDependencies) + $(OutDir) + true + + + ..\..\Include + + + + + + + + + \ No newline at end of file diff --git a/Samples/MWClosestPointApp/MWClosestPointApp.vcxproj.filters b/Samples/MWClosestPointApp/MWClosestPointApp.vcxproj.filters new file mode 100644 index 0000000..8755a3a --- /dev/null +++ b/Samples/MWClosestPointApp/MWClosestPointApp.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/Samples/MWClosestPointApp/Makefile b/Samples/MWClosestPointApp/Makefile new file mode 100644 index 0000000..6a56af5 --- /dev/null +++ b/Samples/MWClosestPointApp/Makefile @@ -0,0 +1,18 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +INC_DIRS = \ + ../../Include \ + ../MWClosestPoint \ + ../Common + +SRC_FILES = *.cpp + +USED_LIBS += OpenNI2 MWClosestPoint + +EXE_NAME = MWClosestPointApp + +CFLAGS += -Wall + +include ../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Samples/MWClosestPointApp/main.cpp b/Samples/MWClosestPointApp/main.cpp new file mode 100644 index 0000000..3a6cac1 --- /dev/null +++ b/Samples/MWClosestPointApp/main.cpp @@ -0,0 +1,126 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include +#include + +#ifdef WIN32 +#include +int wasKeyboardHit() +{ + return (int)_kbhit(); +} + +#else // linux + +#include +#include +#include +#include +int wasKeyboardHit() +{ + struct termios oldt, newt; + int ch; + int oldf; + + // don't echo and don't wait for ENTER + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + + // make it non-blocking (so we can check without waiting) + if (0 != fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK)) + { + return 0; + } + + ch = getchar(); + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + if (0 != fcntl(STDIN_FILENO, F_SETFL, oldf)) + { + return 0; + } + + if(ch != EOF) + { + ungetc(ch, stdin); + return 1; + } + + return 0; +} + +void Sleep(int ms) +{ + usleep(ms*1000); +} + +#endif // WIN32 + + + +class MyMwListener : public closest_point::ClosestPoint::Listener +{ +public: + void readyForNextData(closest_point::ClosestPoint* pClosestPoint) + { + openni::VideoFrameRef frame; + closest_point::IntPoint3D closest; + openni::Status rc = pClosestPoint->getNextData(closest, frame); + + if (rc == openni::STATUS_OK) + { + printf("%d, %d, %d\n", closest.X, closest.Y, closest.Z); + } + else + { + printf("Update failed\n"); + } + } +}; + + +int main() +{ + + closest_point::ClosestPoint closestPoint; + + if (!closestPoint.isValid()) + { + printf("ClosestPoint: error in initialization\n"); + return 1; + } + + MyMwListener myListener; + + closestPoint.setListener(myListener); + + while (!wasKeyboardHit()) + { + Sleep(1000); + } + + closestPoint.resetListener(); + + return 0; +} diff --git a/Samples/MultiDepthViewer/Makefile b/Samples/MultiDepthViewer/Makefile new file mode 100644 index 0000000..ce22c00 --- /dev/null +++ b/Samples/MultiDepthViewer/Makefile @@ -0,0 +1,26 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +INC_DIRS = \ + ../../Include \ + ../../ThirdParty/GL/ \ + ../Common + +SRC_FILES = *.cpp + +ifeq ("$(OSTYPE)","Darwin") + CFLAGS += -DMACOS + LDFLAGS += -framework OpenGL -framework GLUT +else + CFLAGS += -DUNIX -DGLX_GLXEXT_LEGACY + USED_LIBS += glut GL +endif + +USED_LIBS += OpenNI2 + +EXE_NAME = MultiDepthViewer + +CFLAGS += -Wall + +include ../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Samples/MultiDepthViewer/MultiDepthViewer.vcxproj b/Samples/MultiDepthViewer/MultiDepthViewer.vcxproj new file mode 100644 index 0000000..d021c0d --- /dev/null +++ b/Samples/MultiDepthViewer/MultiDepthViewer.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {BDA3BF24-550A-FFF9-83E5-7B56134EEE40} + MultiDepthViewer + + + + Application + true + MultiByte + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\..\Include;..\Common;..\..\ThirdParty\GL;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + glut32.lib;OpenNI2.lib + $(OutDir);..\..\ThirdParty\GL + true + + + + + Disabled + ..\..\Include;..\Common;..\..\ThirdParty\GL;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + glut64.lib;OpenNI2.lib + $(OutDir);..\..\ThirdParty\GL + true + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\Common;..\..\ThirdParty\GL;%(AdditionalIncludeDirectories) + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + glut32.lib;OpenNI2.lib + $(OutDir);..\..\ThirdParty\GL + true + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\Common;..\..\ThirdParty\GL;%(AdditionalIncludeDirectories) + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + glut64.lib;OpenNI2.lib + $(OutDir);..\..\ThirdParty\GL + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/MultiDepthViewer/Viewer.cpp b/Samples/MultiDepthViewer/Viewer.cpp new file mode 100644 index 0000000..ea2d6a5 --- /dev/null +++ b/Samples/MultiDepthViewer/Viewer.cpp @@ -0,0 +1,332 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// Undeprecate CRT functions +#ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE 1 +#endif + +#include "Viewer.h" + +#if (ONI_PLATFORM == ONI_PLATFORM_MACOSX) + #include +#else + #include +#endif + +#include "OniSampleUtilities.h" + +#define GL_WIN_SIZE_X 1280 +#define GL_WIN_SIZE_Y 1024 +#define TEXTURE_SIZE 512 + +#define DEFAULT_DISPLAY_MODE DISPLAY_MODE_DEPTH1 + +#define MIN_NUM_CHUNKS(data_size, chunk_size) ((((data_size)-1) / (chunk_size) + 1)) +#define MIN_CHUNKS_SIZE(data_size, chunk_size) (MIN_NUM_CHUNKS(data_size, chunk_size) * (chunk_size)) + +SampleViewer* SampleViewer::ms_self = NULL; + +void SampleViewer::glutIdle() +{ + glutPostRedisplay(); +} +void SampleViewer::glutDisplay() +{ + SampleViewer::ms_self->display(); +} +void SampleViewer::glutKeyboard(unsigned char key, int x, int y) +{ + SampleViewer::ms_self->onKey(key, x, y); +} + + + + + + +SampleViewer::SampleViewer(const char* strSampleName, openni::VideoStream& depth1, openni::VideoStream& depth2) : + m_pTexMap(NULL), m_eViewState(DEFAULT_DISPLAY_MODE), m_depth1(depth1), m_depth2(depth2), m_streams(NULL) + +{ + ms_self = this; + strncpy(m_strSampleName, strSampleName, ONI_MAX_STR); +} +SampleViewer::~SampleViewer() +{ + delete[] m_pTexMap; + + ms_self = NULL; + + if (m_streams != NULL) + { + delete []m_streams; + } +} + +openni::Status SampleViewer::init(int argc, char **argv) +{ + openni::VideoMode videoMode1 = m_depth1.getVideoMode(); + openni::VideoMode videoMode2 = m_depth2.getVideoMode(); + + if (videoMode1.getResolutionX() != videoMode2.getResolutionX() || + videoMode1.getResolutionY() != videoMode2.getResolutionY()) + { + printf("Streams need to match resolution.\n"); + return openni::STATUS_ERROR; + } + + m_width = videoMode1.getResolutionX(); + m_height = videoMode1.getResolutionY(); + + m_streams = new openni::VideoStream*[2]; + m_streams[0] = &m_depth1; + m_streams[1] = &m_depth2; + + // Texture map init + m_nTexMapX = MIN_CHUNKS_SIZE(m_width, TEXTURE_SIZE); + m_nTexMapY = MIN_CHUNKS_SIZE(m_height, TEXTURE_SIZE); + m_pTexMap = new openni::RGB888Pixel[m_nTexMapX * m_nTexMapY]; + + return initOpenGL(argc, argv); + +} +openni::Status SampleViewer::run() //Does not return +{ + glutMainLoop(); + + return openni::STATUS_OK; +} + +void SampleViewer::displayFrame(const openni::VideoFrameRef& frame) +{ + if (!frame.isValid()) + return; + + const openni::DepthPixel* pDepthRow = (const openni::DepthPixel*)frame.getData(); + openni::RGB888Pixel* pTexRow = m_pTexMap + frame.getCropOriginY() * m_nTexMapX; + int rowSize = frame.getStrideInBytes() / sizeof(openni::DepthPixel); + + for (int y = 0; y < frame.getHeight(); ++y) + { + const openni::DepthPixel* pDepth = pDepthRow; + openni::RGB888Pixel* pTex = pTexRow + frame.getCropOriginX(); + + for (int x = 0; x < frame.getWidth(); ++x, ++pDepth, ++pTex) + { + if (*pDepth != 0) + { + int nHistValue = m_pDepthHist[*pDepth]; + pTex->r = nHistValue; + pTex->g = nHistValue; + pTex->b = nHistValue; + } + } + + pDepthRow += rowSize; + pTexRow += m_nTexMapX; + } + +} + +void SampleViewer::displayBothFrames() +{ + struct + { + const openni::DepthPixel* pDepthRow; + const openni::DepthPixel* pDepth; + } mainFrame, maskFrame; + + mainFrame.pDepthRow = (const openni::DepthPixel*)m_depth1Frame.getData(); + maskFrame.pDepthRow = (const openni::DepthPixel*)m_depth2Frame.getData(); + openni::RGB888Pixel* pTexRow = m_pTexMap + m_depth1Frame.getCropOriginY() * m_nTexMapX; + int rowSize = m_depth1Frame.getStrideInBytes() / sizeof(openni::DepthPixel); + + for (int y = 0; y < m_depth1Frame.getHeight(); ++y) + { + mainFrame.pDepth = mainFrame.pDepthRow; + maskFrame.pDepth = maskFrame.pDepthRow; + openni::RGB888Pixel* pTex = pTexRow + m_depth1Frame.getCropOriginX(); + + for (int x = 0; x < m_depth1Frame.getWidth(); ++x, ++maskFrame.pDepth, ++mainFrame.pDepth, ++pTex) + { + if (*mainFrame.pDepth != 0) + { + int nHistValue = m_pDepthHist[*mainFrame.pDepth]; + + if (*maskFrame.pDepth == 0) + { + // No match + pTex->r = nHistValue; + pTex->g = nHistValue; + pTex->b = nHistValue; + } + else + { + nHistValue = m_pDepthHist[(*mainFrame.pDepth+*maskFrame.pDepth)/2]; + // Match + pTex->r = nHistValue; + pTex->g = 0; + pTex->b = 0; + } + } + } + + mainFrame.pDepthRow += rowSize; + maskFrame.pDepthRow += rowSize; + pTexRow += m_nTexMapX; + } + +} + + +void SampleViewer::display() +{ + int changedIndex; + openni::Status rc = openni::OpenNI::waitForAnyStream(m_streams, 2, &changedIndex); + + if (rc != openni::STATUS_OK) + { + printf("Wait failed\n"); + return; + } + + switch (changedIndex) + { + case 0: + m_depth1.readFrame(&m_depth1Frame); break; + case 1: + m_depth2.readFrame(&m_depth2Frame); break; + default: + printf("Error in wait\n"); + } + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, GL_WIN_SIZE_X, GL_WIN_SIZE_Y, 0, -1.0, 1.0); + + if (m_depth1Frame.isValid() && m_eViewState != DISPLAY_MODE_DEPTH2) + calculateHistogram(m_pDepthHist, MAX_DEPTH, m_depth1Frame); + else + calculateHistogram(m_pDepthHist, MAX_DEPTH, m_depth2Frame); + + memset(m_pTexMap, 0, m_nTexMapX*m_nTexMapY*sizeof(openni::RGB888Pixel)); + + // check if we need to draw image frame to texture + + switch (m_eViewState) + { + case DISPLAY_MODE_DEPTH1: + displayFrame(m_depth1Frame); + break; + case DISPLAY_MODE_DEPTH2: + displayFrame(m_depth2Frame); + break; + default: + displayBothFrames(); + } + + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_nTexMapX, m_nTexMapY, 0, GL_RGB, GL_UNSIGNED_BYTE, m_pTexMap); + + // Display the OpenGL texture map + glColor4f(1,1,1,1); + + glBegin(GL_QUADS); + + int nXRes = m_width; + int nYRes = m_height; + + // upper left + glTexCoord2f(0, 0); + glVertex2f(0, 0); + // upper right + glTexCoord2f((float)nXRes/(float)m_nTexMapX, 0); + glVertex2f(GL_WIN_SIZE_X, 0); + // bottom right + glTexCoord2f((float)nXRes/(float)m_nTexMapX, (float)nYRes/(float)m_nTexMapY); + glVertex2f(GL_WIN_SIZE_X, GL_WIN_SIZE_Y); + // bottom left + glTexCoord2f(0, (float)nYRes/(float)m_nTexMapY); + glVertex2f(0, GL_WIN_SIZE_Y); + + glEnd(); + + // Swap the OpenGL display buffers + glutSwapBuffers(); + +} + +void SampleViewer::onKey(unsigned char key, int /*x*/, int /*y*/) +{ + switch (key) + { + case 27: + m_depth1.stop(); + m_depth2.stop(); + m_depth1.destroy(); + m_depth2.destroy(); + + openni::OpenNI::shutdown(); + exit (1); + case '1': + m_eViewState = DISPLAY_MODE_OVERLAY; + break; + case '2': + m_eViewState = DISPLAY_MODE_DEPTH1; + break; + case '3': + m_eViewState = DISPLAY_MODE_DEPTH2; + break; + case 'm': +// m_rContext.SetGlobalMirror(!m_rContext.GetGlobalMirror()); + break; + } + +} + +openni::Status SampleViewer::initOpenGL(int argc, char **argv) +{ + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); + glutInitWindowSize(GL_WIN_SIZE_X, GL_WIN_SIZE_Y); + glutCreateWindow (m_strSampleName); + // glutFullScreen(); + glutSetCursor(GLUT_CURSOR_NONE); + + initOpenGLHooks(); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + + return openni::STATUS_OK; + +} +void SampleViewer::initOpenGLHooks() +{ + glutKeyboardFunc(glutKeyboard); + glutDisplayFunc(glutDisplay); + glutIdleFunc(glutIdle); +} diff --git a/Samples/MultiDepthViewer/Viewer.h b/Samples/MultiDepthViewer/Viewer.h new file mode 100644 index 0000000..6000120 --- /dev/null +++ b/Samples/MultiDepthViewer/Viewer.h @@ -0,0 +1,82 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _ONI_SAMPLE_VIEWER_H_ +#define _ONI_SAMPLE_VIEWER_H_ + +#include + +#define MAX_DEPTH 10000 + +enum DisplayModes +{ + DISPLAY_MODE_OVERLAY, + DISPLAY_MODE_DEPTH1, + DISPLAY_MODE_DEPTH2 +}; + +class SampleViewer +{ +public: + SampleViewer(const char* strSampleName, openni::VideoStream& depth1, openni::VideoStream& depth2); + virtual ~SampleViewer(); + + virtual openni::Status init(int argc, char **argv); + virtual openni::Status run(); //Does not return + +protected: + virtual void display(); + virtual void displayPostDraw(){}; // Overload to draw over the screen image + + virtual void onKey(unsigned char key, int x, int y); + + virtual openni::Status initOpenGL(int argc, char **argv); + void initOpenGLHooks(); +private: + SampleViewer(const SampleViewer&); + SampleViewer& operator=(SampleViewer&); + + void displayFrame(const openni::VideoFrameRef& frame); + void displayBothFrames(); + + static SampleViewer* ms_self; + static void glutIdle(); + static void glutDisplay(); + static void glutKeyboard(unsigned char key, int x, int y); + + float m_pDepthHist[MAX_DEPTH]; + char m_strSampleName[ONI_MAX_STR]; + openni::RGB888Pixel* m_pTexMap; + unsigned int m_nTexMapX; + unsigned int m_nTexMapY; + DisplayModes m_eViewState; + int m_width; + int m_height; + + openni::VideoStream& m_depth1; + openni::VideoStream& m_depth2; + openni::VideoStream** m_streams; + + openni::VideoFrameRef m_depth1Frame; + openni::VideoFrameRef m_depth2Frame; +}; + + +#endif // _ONI_SAMPLE_VIEWER_H_ \ No newline at end of file diff --git a/Samples/MultiDepthViewer/main.cpp b/Samples/MultiDepthViewer/main.cpp new file mode 100644 index 0000000..7e8e98e --- /dev/null +++ b/Samples/MultiDepthViewer/main.cpp @@ -0,0 +1,139 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include +#include "Viewer.h" + + +int main(int argc, char** argv) +{ + openni::Status rc = openni::STATUS_OK; + + openni::Device device1, device2; + openni::VideoStream depth1, depth2; + + + rc = openni::OpenNI::initialize(); + if (rc != openni::STATUS_OK) + { + printf("%s: Initialize failed\n%s\n", argv[0], openni::OpenNI::getExtendedError()); + return 1; + } + + openni::Array deviceList; + openni::OpenNI::enumerateDevices(&deviceList); + + const char* device1Uri; + const char* device2Uri; + + switch (argc) + { + case 1: + if (deviceList.getSize() < 2) + { + printf("Missing devices\n"); + openni::OpenNI::shutdown(); + return 1; + } + device1Uri = deviceList[1].getUri(); + device2Uri = deviceList[0].getUri(); + break; + case 2: + if (deviceList.getSize() < 1) + { + printf("Missing devices\n"); + openni::OpenNI::shutdown(); + return 1; + } + device1Uri = argv[1]; + if (strcmp(deviceList[0].getUri(), device1Uri) != 0) + device2Uri = deviceList[0].getUri(); + else + device2Uri = deviceList[1].getUri(); + break; + default: + device1Uri = argv[1]; + device2Uri = argv[2]; + } + + rc = device1.open(device1Uri); + if (rc != openni::STATUS_OK) + { + printf("%s: Couldn't open device %s\n%s\n", argv[0], device1Uri, openni::OpenNI::getExtendedError()); + openni::OpenNI::shutdown(); + return 3; + } + + rc = device2.open(device2Uri); + if (rc != openni::STATUS_OK) + { + printf("%s: Couldn't open device %s\n%s\n", argv[0], device2Uri, openni::OpenNI::getExtendedError()); + openni::OpenNI::shutdown(); + return 3; + } + + rc = depth1.create(device1, openni::SENSOR_DEPTH); + if (rc != openni::STATUS_OK) + { + printf("%s: Couldn't create stream %d on device %s\n%s\n", argv[0], openni::SENSOR_DEPTH, device1Uri, openni::OpenNI::getExtendedError()); + openni::OpenNI::shutdown(); + return 4; + } + rc = depth2.create(device2, openni::SENSOR_DEPTH); + if (rc != openni::STATUS_OK) + { + printf("%s: Couldn't create stream %d on device %s\n%s\n", argv[0], openni::SENSOR_DEPTH, device2Uri, openni::OpenNI::getExtendedError()); + openni::OpenNI::shutdown(); + return 4; + } + + rc = depth1.start(); + if (rc != openni::STATUS_OK) + { + printf("%s: Couldn't start stream %d on device %s\n%s\n", argv[0], openni::SENSOR_DEPTH, device1Uri, openni::OpenNI::getExtendedError()); + openni::OpenNI::shutdown(); + return 5; + } + rc = depth2.start(); + if (rc != openni::STATUS_OK) + { + printf("%s: Couldn't start stream %d on device %s\n%s\n", argv[0], openni::SENSOR_DEPTH, device2Uri, openni::OpenNI::getExtendedError()); + openni::OpenNI::shutdown(); + return 5; + } + + if (!depth1.isValid() && !depth2.isValid()) + { + printf("SimpleViewer: No valid streams. Exiting\n"); + openni::OpenNI::shutdown(); + return 6; + } + + SampleViewer sampleViewer("Simple Viewer", depth1, depth2); + + rc = sampleViewer.init(argc, argv); + if (rc != openni::STATUS_OK) + { + printf("SimpleViewer: Initialization failed\n%s\n", openni::OpenNI::getExtendedError()); + openni::OpenNI::shutdown(); + return 7; + } + sampleViewer.run(); +} diff --git a/Samples/MultipleStreamRead/Android.mk b/Samples/MultipleStreamRead/Android.mk new file mode 100644 index 0000000..1b92471 --- /dev/null +++ b/Samples/MultipleStreamRead/Android.mk @@ -0,0 +1,41 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Sources +MY_SRC_FILES := \ + $(LOCAL_PATH)/*.cpp + +MY_SRC_FILE_EXPANDED := $(wildcard $(MY_SRC_FILES)) +LOCAL_SRC_FILES := $(MY_SRC_FILE_EXPANDED:$(LOCAL_PATH)/%=%) + +# C/CPP Flags +LOCAL_CFLAGS += $(OPENNI2_CFLAGS) + +# Includes +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/../../Include \ + $(LOCAL_PATH)/../Common + +# Dependencies +LOCAL_SHARED_LIBRARIES := libOpenNI2 + +# Output +LOCAL_MODULE := MultipleStreamRead + +include $(BUILD_EXECUTABLE) diff --git a/Samples/MultipleStreamRead/Makefile b/Samples/MultipleStreamRead/Makefile new file mode 100644 index 0000000..a298146 --- /dev/null +++ b/Samples/MultipleStreamRead/Makefile @@ -0,0 +1,17 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +INC_DIRS = \ + ../../Include \ + ../Common + +SRC_FILES = *.cpp + +USED_LIBS += OpenNI2 + +EXE_NAME = MultipleStreamRead + +CFLAGS += -Wall + +include ../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Samples/MultipleStreamRead/MultipleStreamRead.vcxproj b/Samples/MultipleStreamRead/MultipleStreamRead.vcxproj new file mode 100644 index 0000000..15887ec --- /dev/null +++ b/Samples/MultipleStreamRead/MultipleStreamRead.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {920D08AC-452C-4326-BC6E-86FE65848587} + MultipleStreamRead + + + + Application + true + MultiByte + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + OpenNI2.lib + $(OutDir) + true + + + + + Disabled + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + OpenNI2.lib + $(OutDir) + true + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + OpenNI2.lib + $(OutDir) + true + + + + + Level4 + MaxSpeed + true + ..\..\Include;..\Common;%(AdditionalIncludeDirectories) + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + OpenNI2.lib + $(OutDir) + true + + + + + + + + + \ No newline at end of file diff --git a/Samples/MultipleStreamRead/main.cpp b/Samples/MultipleStreamRead/main.cpp new file mode 100644 index 0000000..e93941e --- /dev/null +++ b/Samples/MultipleStreamRead/main.cpp @@ -0,0 +1,150 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include +#include + +#include "OniSampleUtilities.h" + +#define SAMPLE_READ_WAIT_TIMEOUT 2000 //2000ms + +using namespace openni; + +void analyzeFrame(const VideoFrameRef& frame) +{ + DepthPixel* pDepth; + RGB888Pixel* pColor; + + int middleIndex = (frame.getHeight()+1)*frame.getWidth()/2; + + switch (frame.getVideoMode().getPixelFormat()) + { + case PIXEL_FORMAT_DEPTH_1_MM: + case PIXEL_FORMAT_DEPTH_100_UM: + pDepth = (DepthPixel*)frame.getData(); + printf("[%08llu] %8d\n", (long long)frame.getTimestamp(), + pDepth[middleIndex]); + break; + case PIXEL_FORMAT_RGB888: + pColor = (RGB888Pixel*)frame.getData(); + printf("[%08llu] 0x%02x%02x%02x\n", (long long)frame.getTimestamp(), + pColor[middleIndex].r&0xff, + pColor[middleIndex].g&0xff, + pColor[middleIndex].b&0xff); + break; + default: + printf("Unknown format\n"); + } +} + + +int main() +{ + Status rc = OpenNI::initialize(); + if (rc != STATUS_OK) + { + printf("Initialize failed\n%s\n", OpenNI::getExtendedError()); + return 1; + } + + Device device; + rc = device.open(ANY_DEVICE); + if (rc != STATUS_OK) + { + printf("Couldn't open device\n%s\n", OpenNI::getExtendedError()); + return 2; + } + + VideoStream depth, color; + + if (device.getSensorInfo(SENSOR_DEPTH) != NULL) + { + rc = depth.create(device, SENSOR_DEPTH); + if (rc == STATUS_OK) + { + rc = depth.start(); + if (rc != STATUS_OK) + { + printf("Couldn't start the color stream\n%s\n", OpenNI::getExtendedError()); + } + } + else + { + printf("Couldn't create depth stream\n%s\n", OpenNI::getExtendedError()); + } + } + + if (device.getSensorInfo(SENSOR_COLOR) != NULL) + { + rc = color.create(device, SENSOR_COLOR); + if (rc == STATUS_OK) + { + rc = color.start(); + if (rc != STATUS_OK) + { + printf("Couldn't start the color stream\n%s\n", OpenNI::getExtendedError()); + } + } + else + { + printf("Couldn't create color stream\n%s\n", OpenNI::getExtendedError()); + } + } + + VideoFrameRef frame; + + VideoStream* streams[] = {&depth, &color}; + + while (!wasKeyboardHit()) + { + int readyStream = -1; + rc = OpenNI::waitForAnyStream(streams, 2, &readyStream, SAMPLE_READ_WAIT_TIMEOUT); + if (rc != STATUS_OK) + { + printf("Wait failed! (timeout is %d ms)\n%s\n", SAMPLE_READ_WAIT_TIMEOUT, OpenNI::getExtendedError()); + break; + } + + switch (readyStream) + { + case 0: + // Depth + depth.readFrame(&frame); + break; + case 1: + // Color + color.readFrame(&frame); + break; + default: + printf("Unxpected stream\n"); + } + + analyzeFrame(frame); + } + + depth.stop(); + color.stop(); + depth.destroy(); + color.destroy(); + device.close(); + OpenNI::shutdown(); + + return 0; +} diff --git a/Samples/SimpleRead/Android.mk b/Samples/SimpleRead/Android.mk new file mode 100644 index 0000000..628da99 --- /dev/null +++ b/Samples/SimpleRead/Android.mk @@ -0,0 +1,41 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Sources +MY_SRC_FILES := \ + $(LOCAL_PATH)/*.cpp + +MY_SRC_FILE_EXPANDED := $(wildcard $(MY_SRC_FILES)) +LOCAL_SRC_FILES := $(MY_SRC_FILE_EXPANDED:$(LOCAL_PATH)/%=%) + +# C/CPP Flags +LOCAL_CFLAGS += $(OPENNI2_CFLAGS) + +# Includes +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/../../Include \ + $(LOCAL_PATH)/../Common + +# Dependencies +LOCAL_SHARED_LIBRARIES := libOpenNI2 + +# Output +LOCAL_MODULE := SimpleRead + +include $(BUILD_EXECUTABLE) diff --git a/Samples/SimpleRead/Makefile b/Samples/SimpleRead/Makefile new file mode 100644 index 0000000..9904e93 --- /dev/null +++ b/Samples/SimpleRead/Makefile @@ -0,0 +1,17 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +INC_DIRS = \ + ../../Include \ + ../Common + +SRC_FILES = *.cpp + +USED_LIBS += OpenNI2 + +EXE_NAME = SimpleRead + +CFLAGS += -Wall + +include ../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Samples/SimpleRead/SimpleRead.vcxproj b/Samples/SimpleRead/SimpleRead.vcxproj new file mode 100644 index 0000000..de3942f --- /dev/null +++ b/Samples/SimpleRead/SimpleRead.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {BDA3BF24-550A-4BF9-83E5-7B56134EDD40} + SimpleRead + + + + Application + true + MultiByte + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\Common;..\..\Include;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + OpenNI2.lib + $(OutDir) + true + + + + + Disabled + ..\Common;..\..\Include;%(AdditionalIncludeDirectories) + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + OpenNI2.lib + $(OutDir) + true + + + + + Level4 + MaxSpeed + true + ..\Common;..\..\Include;%(AdditionalIncludeDirectories) + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + OpenNI2.lib + $(OutDir) + true + + + + + Level4 + MaxSpeed + true + ..\Common;..\..\Include;%(AdditionalIncludeDirectories) + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + OpenNI2.lib + $(OutDir) + true + + + + + + + + + \ No newline at end of file diff --git a/Samples/SimpleRead/main.cpp b/Samples/SimpleRead/main.cpp new file mode 100644 index 0000000..163d7e3 --- /dev/null +++ b/Samples/SimpleRead/main.cpp @@ -0,0 +1,105 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include +#include + +#include "OniSampleUtilities.h" + +#define SAMPLE_READ_WAIT_TIMEOUT 2000 //2000ms + +using namespace openni; + +int main() +{ + Status rc = OpenNI::initialize(); + if (rc != STATUS_OK) + { + printf("Initialize failed\n%s\n", OpenNI::getExtendedError()); + return 1; + } + + Device device; + rc = device.open(ANY_DEVICE); + if (rc != STATUS_OK) + { + printf("Couldn't open device\n%s\n", OpenNI::getExtendedError()); + return 2; + } + + VideoStream depth; + + if (device.getSensorInfo(SENSOR_DEPTH) != NULL) + { + rc = depth.create(device, SENSOR_DEPTH); + if (rc != STATUS_OK) + { + printf("Couldn't create depth stream\n%s\n", OpenNI::getExtendedError()); + return 3; + } + } + + rc = depth.start(); + if (rc != STATUS_OK) + { + printf("Couldn't start the depth stream\n%s\n", OpenNI::getExtendedError()); + return 4; + } + + VideoFrameRef frame; + + while (!wasKeyboardHit()) + { + int changedStreamDummy; + VideoStream* pStream = &depth; + rc = OpenNI::waitForAnyStream(&pStream, 1, &changedStreamDummy, SAMPLE_READ_WAIT_TIMEOUT); + if (rc != STATUS_OK) + { + printf("Wait failed! (timeout is %d ms)\n%s\n", SAMPLE_READ_WAIT_TIMEOUT, OpenNI::getExtendedError()); + continue; + } + + rc = depth.readFrame(&frame); + if (rc != STATUS_OK) + { + printf("Read failed!\n%s\n", OpenNI::getExtendedError()); + continue; + } + + if (frame.getVideoMode().getPixelFormat() != PIXEL_FORMAT_DEPTH_1_MM && frame.getVideoMode().getPixelFormat() != PIXEL_FORMAT_DEPTH_100_UM) + { + printf("Unexpected frame format\n"); + continue; + } + + DepthPixel* pDepth = (DepthPixel*)frame.getData(); + + int middleIndex = (frame.getHeight()+1)*frame.getWidth()/2; + + printf("[%08llu] %8d\n", (long long)frame.getTimestamp(), pDepth[middleIndex]); + } + + depth.stop(); + depth.destroy(); + device.close(); + OpenNI::shutdown(); + + return 0; +} diff --git a/Samples/SimpleViewer.java/Build.bat b/Samples/SimpleViewer.java/Build.bat new file mode 100644 index 0000000..fcd5af2 --- /dev/null +++ b/Samples/SimpleViewer.java/Build.bat @@ -0,0 +1 @@ +@"%0\..\..\..\ThirdParty\PSCommon\BuildSystem\BuildJavaWindows.py" "%1" "%0\..\..\..\Bin" "%0\..\src\org\openni\Samples\SimpleViewer" org.openni.Samples.SimpleViewer org.openni.jar org.openni.Samples.SimpleViewer.SimpleViewerApplication diff --git a/Samples/SimpleViewer.java/Makefile b/Samples/SimpleViewer.java/Makefile new file mode 100644 index 0000000..bfcea5b --- /dev/null +++ b/Samples/SimpleViewer.java/Makefile @@ -0,0 +1,12 @@ +include ../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../Bin + +SRC_FILES = \ + src/org/openni/Samples/SimpleViewer/*.java + +JAR_NAME = org.openni.Samples.SimpleViewer +USED_JARS = org.openni +MAIN_CLASS = org.openni.Samples.SimpleViewer.SimpleViewerApplication + +include ../../ThirdParty/PSCommon/BuildSystem/CommonJavaMakefile diff --git a/Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewer.java b/Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewer.java new file mode 100644 index 0000000..3e674d1 --- /dev/null +++ b/Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewer.java @@ -0,0 +1,140 @@ +package org.openni.Samples.SimpleViewer; + +import java.awt.*; +import java.awt.image.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.openni.*; + + +public class SimpleViewer extends Component + implements VideoStream.NewFrameListener { + + float mHistogram[]; + int[] mImagePixels; + VideoStream mVideoStream; + VideoFrameRef mLastFrame; + BufferedImage mBufferedImage; + + public SimpleViewer() { + } + + public void setStream(VideoStream videoStream) { + if (mLastFrame != null) { + mLastFrame.release(); + mLastFrame = null; + } + + if (mVideoStream != null) { + mVideoStream.removeNewFrameListener(this); + } + + mVideoStream = videoStream; + + if (mVideoStream != null) { + mVideoStream.addNewFrameListener(this); + } + } + + @Override + public synchronized void paint(Graphics g) { + if (mLastFrame == null) { + return; + } + + int width = mLastFrame.getWidth(); + int height = mLastFrame.getHeight(); + + // make sure we have enough room + if (mBufferedImage == null || mBufferedImage.getWidth() != width || mBufferedImage.getHeight() != height) { + mBufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + } + + mBufferedImage.setRGB(0, 0, width, height, mImagePixels, 0, width); + + int framePosX = (getWidth() - width) / 2; + int framePosY = (getHeight() - height) / 2; + g.drawImage(mBufferedImage, framePosX, framePosY, null); + } + + @Override + public synchronized void onFrameReady(VideoStream stream) { + if (mLastFrame != null) { + mLastFrame.release(); + mLastFrame = null; + } + + mLastFrame = mVideoStream.readFrame(); + ByteBuffer frameData = mLastFrame.getData().order(ByteOrder.LITTLE_ENDIAN); + + // make sure we have enough room + if (mImagePixels == null || mImagePixels.length < mLastFrame.getWidth() * mLastFrame.getHeight()) { + mImagePixels = new int[mLastFrame.getWidth() * mLastFrame.getHeight()]; + } + + switch (mLastFrame.getVideoMode().getPixelFormat()) + { + case DEPTH_1_MM: + case DEPTH_100_UM: + case SHIFT_9_2: + case SHIFT_9_3: + calcHist(frameData); + frameData.rewind(); + int pos = 0; + while(frameData.remaining() > 0) { + int depth = (int)frameData.getShort() & 0xFFFF; + short pixel = (short)mHistogram[depth]; + mImagePixels[pos] = 0xFF000000 | (pixel << 16) | (pixel << 8); + pos++; + } + break; + case RGB888: + pos = 0; + while (frameData.remaining() > 0) { + int red = (int)frameData.get() & 0xFF; + int green = (int)frameData.get() & 0xFF; + int blue = (int)frameData.get() & 0xFF; + mImagePixels[pos] = 0xFF000000 | (red << 16) | (green << 8) | blue; + pos++; + } + break; + default: + // don't know how to draw + mLastFrame.release(); + mLastFrame = null; + } + + repaint(); + } + + private void calcHist(ByteBuffer depthBuffer) { + // make sure we have enough room + if (mHistogram == null || mHistogram.length < mVideoStream.getMaxPixelValue()) { + mHistogram = new float[mVideoStream.getMaxPixelValue()]; + } + + // reset + for (int i = 0; i < mHistogram.length; ++i) + mHistogram[i] = 0; + + int points = 0; + while (depthBuffer.remaining() > 0) { + int depth = depthBuffer.getShort() & 0xFFFF; + if (depth != 0) { + mHistogram[depth]++; + points++; + } + } + + for (int i = 1; i < mHistogram.length; i++) { + mHistogram[i] += mHistogram[i - 1]; + } + + if (points > 0) { + for (int i = 1; i < mHistogram.length; i++) { + mHistogram[i] = (int) (256 * (1.0f - (mHistogram[i] / (float) points))); + } + } + } +} diff --git a/Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewerApplication.java b/Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewerApplication.java new file mode 100755 index 0000000..2f52385 --- /dev/null +++ b/Samples/SimpleViewer.java/src/org/openni/Samples/SimpleViewer/SimpleViewerApplication.java @@ -0,0 +1,203 @@ +package org.openni.Samples.SimpleViewer; + +import org.openni.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JOptionPane; + +public class SimpleViewerApplication implements ItemListener { + + private JFrame mFrame; + private JPanel mPanel; + private SimpleViewer mViewer; + private boolean mShouldRun = true; + private Device mDevice; + private VideoStream mVideoStream; + private ArrayList mDeviceSensors; + private ArrayList mSupportedModes; + + private JComboBox mComboBoxStreams; + private JComboBox mComboBoxVideoModes; + + public SimpleViewerApplication(Device device) { + mDevice = device; + + mFrame = new JFrame("OpenNI Simple Viewer"); + mPanel = new JPanel(); + mViewer = new SimpleViewer(); + + // register to key events + mFrame.addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent arg0) {} + + @Override + public void keyReleased(KeyEvent arg0) {} + + @Override + public void keyPressed(KeyEvent arg0) { + if (arg0.getKeyCode() == KeyEvent.VK_ESCAPE) { + mShouldRun = false; + } + } + }); + + // register to closing event + mFrame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + mShouldRun = false; + } + }); + + mComboBoxStreams = new JComboBox(); + mComboBoxVideoModes = new JComboBox(); + + mComboBoxStreams.addItem(""); + mDeviceSensors = new ArrayList(); + + if (device.getSensorInfo(SensorType.COLOR) != null) { + mDeviceSensors.add(SensorType.COLOR); + mComboBoxStreams.addItem("Color"); + } + + if (device.getSensorInfo(SensorType.DEPTH) != null) { + mDeviceSensors.add(SensorType.DEPTH); + mComboBoxStreams.addItem("Depth"); + } + + mComboBoxStreams.addItemListener(this); + mComboBoxVideoModes.addItemListener(this); + mViewer.setSize(800,600); + + mPanel.add("West", mComboBoxStreams); + mPanel.add("East", mComboBoxVideoModes); + mFrame.add("North", mPanel); + mFrame.add("Center", mViewer); + mFrame.setSize(mViewer.getWidth() + 20, mViewer.getHeight() + 80); + mFrame.setVisible(true); + } + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.DESELECTED) + return; + + if (e.getSource() == mComboBoxStreams) { + selectedStreamChanged(); + } else if (e.getSource() == mComboBoxVideoModes) { + selectedVideoModeChanged(); + } + } + + void selectedStreamChanged() { + + if (mVideoStream != null) { + mVideoStream.stop(); + mViewer.setStream(null); + mVideoStream.destroy(); + mVideoStream = null; + } + + int sensorIndex = mComboBoxStreams.getSelectedIndex() - 1; + if (sensorIndex == -1) { + return; + } + + SensorType type = mDeviceSensors.get(sensorIndex); + + mVideoStream = VideoStream.create(mDevice, type); + List supportedModes = mVideoStream.getSensorInfo().getSupportedVideoModes(); + mSupportedModes = new ArrayList(); + + // now only keeo the ones that our application supports + for (VideoMode mode : supportedModes) { + switch (mode.getPixelFormat()) { + case DEPTH_1_MM: + case DEPTH_100_UM: + case SHIFT_9_2: + case SHIFT_9_3: + case RGB888: + mSupportedModes.add(mode); + break; + } + } + + // and add them to combo box + mComboBoxVideoModes.removeAllItems(); + mComboBoxVideoModes.addItem(" +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 4 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = NO + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = NO + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = NO + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/Source/Documentation/OpenNILogo.bmp b/Source/Documentation/OpenNILogo.bmp new file mode 100644 index 0000000000000000000000000000000000000000..dd17c33597391bf83f1f54a865a779db5154706d GIT binary patch literal 21030 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 OpenNI objects should be destroyed before calling @ref openni::OpenNI::shutdown(). + +Other objects are not created by the user, but are actually members of other objects. For example, calling @ref openni::Device::getDeviceInfo() returns a reference +to a @ref openni::DeviceInfo object. Note that once the @ref openni::Device object that was used gets closed, the @ref openni::DeviceInfo reference is no longer valid. Do +not try to use it to access methods of the @ref openni::DeviceInfo object. + +@section arrays Arrays + +Some methods return an array of items. We created a very simple template class, @ref openni::Array, which only holds the pointer to the underlying C array +and the number of elements in that array. Once the array has been released, the elements are no longer accessible. Do not keep a pointer to a specific +item in the array and use it after releasing it. + +@section properties_and_commands Properties and Commands + +OpenNI has a mechanism for generic properties and commands, both for devices and for streams. Commands are used by calling @ref openni::Device::invoke() or +@ref openni::VideoStream::invoke() and Properties can be read via @ref openni::Device::getProperty() and @ref openni::VideoStream::getProperty() and written via +@ref openni::Device::setProperty() and @ref openni::VideoStream::setProperty(). + +Though both might use both primitives and complex types, generally, properties are used to encapsulate a logical data unit (even though complex) while commands are used in the following cases: +- The action requires some arguments (that are not part of the data unit). +- Obtaining a data unit via getProperty() have side effects. +- The operation is expensive enough that you want to communicate it to the user. +- The action has side effects. +- The data unit might be changed over time without an outside request. + +*/ \ No newline at end of file diff --git a/Source/Documentation/Text/GettingStarted.txt b/Source/Documentation/Text/GettingStarted.txt new file mode 100644 index 0000000..cf32d30 --- /dev/null +++ b/Source/Documentation/Text/GettingStarted.txt @@ -0,0 +1,89 @@ +/** +@page getting_started Getting Started + +@section installing Installing OpenNI SDK + +@subsection installing_windows Installing OpenNI SDK on Windows + +Double-click the provided msi file to install the SDK on your Windows machine. + +The installation performs the following: +- Copies the SDK to the target directory (default is C:\\Program Files\\OpenNI2 or C:\\Program Files (x86)\\OpenNI2) +- Installs a USB driver to be used with OpenNI-compliant devices +- Defines environment variables to be used when developing OpenNI applications (see @ref creating_proj_vs) + +@subsection installing_linux Installing OpenNI SDK on Linux + +-# Extract the tarball to a directory of your choice +-# Go into this directory and run the install script: + @code + ./install.sh + @endcode + +The installation creates udev rules which will allow usage of OpenNI-compliant USB devices without +root priviledges. + +@section Running Samples + +OpenNI SDK arrives with pre-compiled samples that can be run immediately after installation. + +Under the installation directory, go to the Samples/Bin directory and run any of the samples +there. +Note that some samples have a graphical interface and may require a more powerfull graphic accelerator. + +@section creating_proj Creating new project that uses OpenNI + +@subsection creating_proj_vs Visual Studio + +-# Open a new project or an existing one with which you want to use OpenNI +-# In the Visual Studio menu, open the Project menu and choose Project Properties. +-# In the C/C++ section, under the "General" node, select "Additional Include Directories" and + add "$(OPENNI2_INCLUDE)" (if you use the 32-bit version) or "$(OPENNI2_INCLUDE64)" (if you use the 64-bit version). + These are environment variables that point to the location of the OpenNI Include directory. + (The defaults are C:\\Program Files\\OpenNI2\\Include or C:\\Program Files (x86)\\OpenNI2\\Include) +-# In the Linker section, under the "General" node, select "Additional Library Directories" and add + "$(OPENNI2_LIB)" (if you use the 32-bit version) or "$(OPENNI2_LIB64)" (if you use the 64-bit version). + These are environment variables that point to the location of the OpenNI Lib directory. + (The defaults are C:\\Program Files\\OpenNI2\\Lib or C:\\Program Files (x86)\\OpenNI2\\Lib) +-# In the Linker section, under the input node, select "Additional Dependencies" and add OpenNI2.lib or OpenNI2.lib +-# Ensure that you add the Additional Include and Library directories to both your Release and Debug configurations. +-# Copy all the files from OpenNI's redist directory (see environment variable "$(OPENNI2_REDIST)" or "$(OPENNI2_REDIST64)") + to your working directory. (The defaults are C:\\Program Files\\OpenNI2\\Redist or C:\\Program Files (x86)\\OpenNI2\\Redist). + Be aware that when you run from command line, the working directory is the directory where the executable can be found, + and where you run from Visual Studio the default directory is where the project file (.vcproj, .vcxproj) can be found. + @note You may ask Visual Studio to change working directory when debugging to the directory where the executable is by + chaning "Project Properties" -> "Debugging" -> "Working Directory" to "$(TargetDir)". + Note that this setting is not kept as part of the project settings, but on a per-user, per-configuration basis. + +@subsection creating_proj_linux GCC / GNU Make + +In the following section, $(OPENNI_DIR) refers to the directory to where OpenNI SDK was extracted. +Note that the installation does not define such an environment variable. Either define it yourself +or use the full path. + +-# Add the SDK Include directory, $OPENNI_DIR/Include, to your include path (-I) +-# Copy the files from the Redist directory, $OPENNI_DIR/Redist, to your execution directory +-# Add the execution directory to your lib path (-L) +-# Add libOpenNI2 to your library list (-l) +-# It is highly suggested to also add the "-Wl,-rpath ./" to your linkage command. Otherwise, the runtime + linker will not find the libOpenNI.so file when you run your application. (default Linux behavior is to + look for shared objects only in /lib and /usr/lib). + +@subsection creating_proj_code Writing an Application + +- Your code should include OpenNI.h header file. +- The entire C++ API is available under the openni namespace. +- Be sure to call @ref openni::OpenNI::initialize(), to make sure all drivers are loaded + If no drivers are found, this function will fail. If it does, you can get some basic log + by calling @ref openni::OpenNI::getExtendedError() (which returns a string) + Note that usually this method fails because OpenNI redist files weren't copied to the working directory. +- When closing your application, call @ref openni::OpenNI::shutdown(), to allow OpenNI to close properly (unload drivers and such). +- Open a device using its URI. + You can get a list of available devices using @ref openni::OpenNI::enumerateDevices(). Enumeration returns an array of + @ref openni::DeviceInfo objects, which include (among other things) the device URI. + If you don't care which device to use, you can specify openni::ANY_DEVICE as the URI. + (to work with .oni files, use the path to the file as its URI) +- Create a video stream by specifying the device and the sensor. +- Be sure to @ref openni::VideoStream::destroy() "destroy" the stream and @ref openni::Device::close() "close" the device when you're done. + +*/ \ No newline at end of file diff --git a/Source/Documentation/Text/MainPage.txt b/Source/Documentation/Text/MainPage.txt new file mode 100644 index 0000000..e993f43 --- /dev/null +++ b/Source/Documentation/Text/MainPage.txt @@ -0,0 +1,11 @@ +/** +@mainpage OpenNI Overview + +This overview comprises the following major sections: + +- @subpage getting_started +- @subpage conventions_cpp +- @subpage legal +- @subpage release_notes +*/ + diff --git a/Source/Drivers/Android.mk b/Source/Drivers/Android.mk new file mode 100644 index 0000000..a609aef --- /dev/null +++ b/Source/Drivers/Android.mk @@ -0,0 +1,17 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Recurse through all subdirs +include $(call all-subdir-makefiles) diff --git a/Source/Drivers/DummyDevice/DummyDevice.cpp b/Source/Drivers/DummyDevice/DummyDevice.cpp new file mode 100644 index 0000000..675843f --- /dev/null +++ b/Source/Drivers/DummyDevice/DummyDevice.cpp @@ -0,0 +1,478 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "Driver/OniDriverAPI.h" +#include "XnLib.h" +#include "XnHash.h" +#include "XnEvent.h" +#include "XnPlatform.h" + +#define OZ_RESOLUTION_X 320 +#define OZ_RESOLUTION_Y 240 + +class OzStream : public oni::driver::StreamBase +{ +public: + ~OzStream() + { + stop(); + } + + OniStatus start() + { + xnOSCreateThread(threadFunc, this, &m_threadHandle); + + return ONI_STATUS_OK; + } + + void stop() + { + m_running = false; + } + + virtual OniStatus SetVideoMode(OniVideoMode*) = 0; + virtual OniStatus GetVideoMode(OniVideoMode* pVideoMode) = 0; + + OniStatus getProperty(int propertyId, void* data, int* pDataSize) + { + if (propertyId == ONI_STREAM_PROPERTY_VIDEO_MODE) + { + if (*pDataSize != sizeof(OniVideoMode)) + { + printf("Unexpected size: %d != %d\n", *pDataSize, (int)sizeof(OniVideoMode)); + return ONI_STATUS_ERROR; + } + return GetVideoMode((OniVideoMode*)data); + } + + return ONI_STATUS_NOT_IMPLEMENTED; + } + + OniStatus setProperty(int propertyId, const void* data, int dataSize) + { + if (propertyId == ONI_STREAM_PROPERTY_VIDEO_MODE) + { + if (dataSize != sizeof(OniVideoMode)) + { + printf("Unexpected size: %d != %d\n", dataSize, (int)sizeof(OniVideoMode)); + return ONI_STATUS_ERROR; + } + return SetVideoMode((OniVideoMode*)data); + } + + return ONI_STATUS_NOT_IMPLEMENTED; + } + + virtual void Mainloop() = 0; +protected: + // Thread + static XN_THREAD_PROC threadFunc(XN_THREAD_PARAM pThreadParam) + { + OzStream* pStream = (OzStream*)pThreadParam; + pStream->m_running = true; + pStream->Mainloop(); + + XN_THREAD_PROC_RETURN(XN_STATUS_OK); + } + + + int singleRes(int x, int y) {return y*OZ_RESOLUTION_X+x;} + + bool m_running; + + XN_THREAD_HANDLE m_threadHandle; +}; + +class OzDepthStream : public OzStream +{ +public: + OniStatus SetVideoMode(OniVideoMode*) {return ONI_STATUS_NOT_IMPLEMENTED;} + OniStatus GetVideoMode(OniVideoMode* pVideoMode) + { + pVideoMode->pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + pVideoMode->fps = 30; + pVideoMode->resolutionX = OZ_RESOLUTION_X; + pVideoMode->resolutionY = OZ_RESOLUTION_Y; + return ONI_STATUS_OK; + } + +private: + + void Mainloop() + { + int frameId = 1; + int xdir = 1; + int ydir = 1; + struct {int x, y;} center = {0,0}; + while (m_running) + { +// printf("Tick"); + OniFrame* pFrame = getServices().acquireFrame(); + + if (pFrame == NULL) {printf("Didn't get frame...\n"); continue;} + + // Fill frame + xnOSMemSet(pFrame->data, 0, pFrame->dataSize); + + OniDepthPixel* pDepth = (OniDepthPixel*)pFrame->data; + + for (int y1 = XN_MAX(center.y-10, 0); y1 < XN_MIN(center.y+10, OZ_RESOLUTION_Y); ++y1) + for (int x1 = XN_MAX(center.x-10, 0); x1 < XN_MIN(center.x+10, OZ_RESOLUTION_X); ++x1) + if ((x1-center.x)*(x1-center.x)+(y1-center.y)*(y1-center.y) < 70) + pDepth[singleRes(x1, y1)] = OniDepthPixel(1000+(x1-y1)*3); + +// pDepth[singleRes(center.x, center.y)] = 1000; + + center.x += xdir; + center.y += ydir; + + if (center.x < abs(xdir) || center.x > OZ_RESOLUTION_X-1-abs(xdir)) xdir*=-1; + if (center.y < abs(ydir) || center.y > OZ_RESOLUTION_Y-1-abs(ydir)) ydir*=-1; + + for (int i = 0; i < OZ_RESOLUTION_X; ++i) pDepth[i] = 2000; + pDepth[0] = 2000; + + // Fill metadata + pFrame->frameIndex = frameId; + + pFrame->videoMode.pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + pFrame->videoMode.resolutionX = OZ_RESOLUTION_X; + pFrame->videoMode.resolutionY = OZ_RESOLUTION_Y; + pFrame->videoMode.fps = 30; + + pFrame->width = OZ_RESOLUTION_X; + pFrame->height = OZ_RESOLUTION_Y; + + pFrame->cropOriginX = pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + + pFrame->sensorType = ONI_SENSOR_DEPTH; + pFrame->stride = OZ_RESOLUTION_X*sizeof(OniDepthPixel); + pFrame->timestamp = frameId*33000; + + raiseNewFrame(pFrame); + getServices().releaseFrame(pFrame); + + frameId++; + + xnOSSleep(33); + } + } +}; + +class OzImageStream : public OzStream +{ +public: + OniStatus SetVideoMode(OniVideoMode*) {return ONI_STATUS_NOT_IMPLEMENTED;} + OniStatus GetVideoMode(OniVideoMode* pVideoMode) + { + pVideoMode->pixelFormat = ONI_PIXEL_FORMAT_RGB888; + pVideoMode->fps = 30; + pVideoMode->resolutionX = OZ_RESOLUTION_X; + pVideoMode->resolutionY = OZ_RESOLUTION_Y; + return ONI_STATUS_OK; + } + +private: + + void Mainloop() + { + int frameId = 1; + int xdir = -3; + int ydir = 1; + struct {int x, y;} center = {160,120}; + while (m_running) + { + xnOSSleep(33); + // printf("Tick"); + OniFrame* pFrame = getServices().acquireFrame(); + + if (pFrame == NULL) {printf("Didn't get frame...\n"); continue;} + + // Fill frame + xnOSMemSet(pFrame->data, 0, pFrame->dataSize); + + OniRGB888Pixel* pImage = (OniRGB888Pixel*)pFrame->data; + + + for (int y = XN_MAX(center.y-10, 0); y < XN_MIN(center.y+10, OZ_RESOLUTION_Y); ++y) + for (int x = XN_MAX(center.x-10, 0); x < XN_MIN(center.x+10, OZ_RESOLUTION_X); ++x) + if ((x-center.x)*(x-center.x)+(y-center.y)*(y-center.y) < 70) + { + pImage[singleRes(x, y)].r = (char)(255*(x/(double)OZ_RESOLUTION_X)); + pImage[singleRes(x, y)].g = (char)(255*(y/(double)OZ_RESOLUTION_Y)); + pImage[singleRes(x, y)].b = (char)(255*((OZ_RESOLUTION_X-x)/(double)OZ_RESOLUTION_X)); + } +// pImage[singleRes(center.x, center.y)].r = 255; + + center.x += xdir; + center.y += ydir; + + if (center.x < abs(xdir) || center.x > OZ_RESOLUTION_X-1-abs(xdir)) xdir*=-1; + if (center.y < abs(ydir) || center.y > OZ_RESOLUTION_Y-1-abs(ydir)) ydir*=-1; + + + + pImage[0].b = (unsigned char)255; + + // for (int y = 0; y < OZ_RESOLUTION_Y; ++y) + // { + // pDepth[y*OZ_RESOLUTION_X+(OZ_RESOLUTION_Y-y)] = pDepth[y*OZ_RESOLUTION_X+(y)] = 500+y; + // } + + // Fill metadata + pFrame->frameIndex = frameId; + + pFrame->videoMode.pixelFormat = ONI_PIXEL_FORMAT_RGB888; + pFrame->videoMode.resolutionX = OZ_RESOLUTION_X; + pFrame->videoMode.resolutionY = OZ_RESOLUTION_Y; + pFrame->videoMode.fps = 30; + + pFrame->width = OZ_RESOLUTION_X; + pFrame->height = OZ_RESOLUTION_Y; + + pFrame->cropOriginX = pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + + pFrame->sensorType = ONI_SENSOR_COLOR; + pFrame->stride = OZ_RESOLUTION_X*3; + pFrame->timestamp = frameId*33000; + + raiseNewFrame(pFrame); + getServices().releaseFrame(pFrame); + + frameId++; + } + } +}; + +class OzDevice : public oni::driver::DeviceBase +{ +public: + OzDevice(OniDeviceInfo* pInfo, oni::driver::DriverServices& driverServices) : m_pInfo(pInfo), m_driverServices(driverServices) + { + m_numSensors = 2; + + m_sensors[0].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, 1); + m_sensors[0].sensorType = ONI_SENSOR_DEPTH; + m_sensors[0].numSupportedVideoModes = 1; + m_sensors[0].pSupportedVideoModes[0].pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_sensors[0].pSupportedVideoModes[0].fps = 30; + m_sensors[0].pSupportedVideoModes[0].resolutionX = OZ_RESOLUTION_X; + m_sensors[0].pSupportedVideoModes[0].resolutionY = OZ_RESOLUTION_Y; + + m_sensors[1].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, 1); + m_sensors[1].sensorType = ONI_SENSOR_COLOR; + m_sensors[1].numSupportedVideoModes = 1; + m_sensors[1].pSupportedVideoModes[0].pixelFormat = ONI_PIXEL_FORMAT_RGB888; + m_sensors[1].pSupportedVideoModes[0].fps = 30; + m_sensors[1].pSupportedVideoModes[0].resolutionX = OZ_RESOLUTION_X; + m_sensors[1].pSupportedVideoModes[0].resolutionY = OZ_RESOLUTION_Y; + + } + OniDeviceInfo* GetInfo() + { + return m_pInfo; + } + + OniStatus getSensorInfoList(OniSensorInfo** pSensors, int* numSensors) + { + *numSensors = m_numSensors; + *pSensors = m_sensors; + + return ONI_STATUS_OK; + } + + oni::driver::StreamBase* createStream(OniSensorType sensorType) + { + if (sensorType == ONI_SENSOR_DEPTH) + { + OzDepthStream* pDepth = XN_NEW(OzDepthStream); + return pDepth; + } + if (sensorType == ONI_SENSOR_COLOR) + { + OzImageStream* pImage = XN_NEW(OzImageStream); + return pImage; + } + + m_driverServices.errorLoggerAppend("OzDevice: Can't create a stream of type %d", sensorType); + return NULL; + } + + void destroyStream(oni::driver::StreamBase* pStream) + { + XN_DELETE(pStream); + } + + OniStatus getProperty(int propertyId, void* data, int* pDataSize) + { + OniStatus rc = ONI_STATUS_OK; + + switch (propertyId) + { + case ONI_DEVICE_PROPERTY_DRIVER_VERSION: + { + if (*pDataSize == sizeof(OniVersion)) + { + OniVersion* version = (OniVersion*)data; + version->major = version->minor = version->maintenance = version->build = 2; + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniVersion)); + rc = ONI_STATUS_ERROR; + } + } + break; + default: + m_driverServices.errorLoggerAppend("Unknown property: %d\n", propertyId); + rc = ONI_STATUS_ERROR; + } + return rc; + } +private: + OzDevice(const OzDevice&); + void operator=(const OzDevice&); + + OniDeviceInfo* m_pInfo; + int m_numSensors; + OniSensorInfo m_sensors[10]; + oni::driver::DriverServices& m_driverServices; +}; + + +class OzDriver : public oni::driver::DriverBase +{ +public: + OzDriver(OniDriverServices* pDriverServices) : DriverBase(pDriverServices) + {} + + virtual oni::driver::DeviceBase* deviceOpen(const char* uri, const char* /*mode*/) + { + for (xnl::Hash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (xnOSStrCmp(iter->Key()->uri, uri) == 0) + { + // Found + if (iter->Value() != NULL) + { + // already using + return iter->Value(); + } + + OzDevice* pDevice = XN_NEW(OzDevice, iter->Key(), getServices()); + iter->Value() = pDevice; + return pDevice; + } + } + + getServices().errorLoggerAppend("Looking for '%s'", uri); + return NULL; + } + + virtual void deviceClose(oni::driver::DeviceBase* pDevice) + { + for (xnl::Hash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (iter->Value() == pDevice) + { + iter->Value() = NULL; + XN_DELETE(pDevice); + return; + } + } + + // not our device?! + XN_ASSERT(FALSE); + } + + virtual OniStatus tryDevice(const char* uri) + { + if (xnOSStrCmp(uri, "Dummy") != 0 && + xnOSStrCmp(uri, "Oz") != 0 && + xnOSStrCmp(uri, "PingPong") != 0) + { + return ONI_STATUS_ERROR; + } + + + OniDeviceInfo* pInfo = XN_NEW(OniDeviceInfo); + xnOSStrCopy(pInfo->uri, uri, ONI_MAX_STR); + xnOSStrCopy(pInfo->vendor, "Table Tennis", ONI_MAX_STR); + m_devices[pInfo] = NULL; + + deviceConnected(pInfo); + + return ONI_STATUS_OK; + } + + void shutdown() {} + +protected: + /* + static XN_THREAD_PROC threadFunc(XN_THREAD_PARAM pThreadParam) + { + OzModule* pModule = (OzModule*)pThreadParam; + + for (int i = 0; i < 10; ++i) + { + printf("%d\n", i); + Sleep(1000); + } + + OzDevice* pDevice4 = new OzDevice; + xnOSStrCopy(pDevice4->GetInfo()->uri, "Oz4", 256); + pModule->m_deviceConnected.Raise(pDevice4, pDevice4->GetInfo()); + + for (int i = 0; i < 3; ++i) + { + printf("%d\n", i); + Sleep(1000); + } + + OzDevice* pDevice2 = new OzDevice; + xnOSStrCopy(pDevice2->GetInfo()->uri, "Oz2", 256); + pModule->m_deviceConnected.Raise(pDevice2, pDevice2->GetInfo()); + + for (int i = 0; i < 5; ++i) + { + printf("%d\n", i); + Sleep(1000); + } + + pModule->m_deviceDisconnected.Raise(pDevice2); + delete pDevice2; + + OzDevice* pDevice3 = new OzDevice; + xnOSStrCopy(pDevice3->GetInfo()->uri, "Oz3", 256); + pModule->m_deviceConnected.Raise(pDevice3, pDevice3->GetInfo()); + + + XN_THREAD_PROC_RETURN(XN_STATUS_OK); + } + */ + + XN_THREAD_HANDLE m_threadHandle; + + xnl::Hash m_devices; +}; + +ONI_EXPORT_DRIVER(OzDriver); diff --git a/Source/Drivers/DummyDevice/DummyDevice.vcxproj b/Source/Drivers/DummyDevice/DummyDevice.vcxproj new file mode 100644 index 0000000..56c5944 --- /dev/null +++ b/Source/Drivers/DummyDevice/DummyDevice.vcxproj @@ -0,0 +1,194 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {B7DE6235-086E-42C6-B5AC-2DC795388ED9} + DummyDevice + + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + _WINDLL;%(PreprocessorDefinitions);DummyDevice2_EXPORT + ProgramDatabase + Level4 + true + true + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + XnLib.lib;%(AdditionalDependencies) + true + + + ..\..\..\Include + + + + + Disabled + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + _WINDLL;%(PreprocessorDefinitions);DummyDevice2_EXPORT + ProgramDatabase + Level4 + true + true + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + XnLib.lib;%(AdditionalDependencies) + true + + + ..\..\..\Include + + + + + Level4 + MaxSpeed + true + _MBCS;%(PreprocessorDefinitions);DummyDevice2_EXPORT + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + ..\..\..\Include + + + + + Level4 + MaxSpeed + true + _MBCS;%(PreprocessorDefinitions);DummyDevice2_EXPORT + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + ..\..\..\Include + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Drivers/DummyDevice/Makefile b/Source/Drivers/DummyDevice/Makefile new file mode 100644 index 0000000..bb4ebc7 --- /dev/null +++ b/Source/Drivers/DummyDevice/Makefile @@ -0,0 +1,30 @@ +include ../../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../../Bin + +INC_DIRS = \ + ../../../Include \ + ../../../ThirdParty/PSCommon/XnLib/Include + +SRC_FILES = \ + *.cpp + +ifeq ("$(OSTYPE)","Darwin") + INC_DIRS += /opt/local/include + LIB_DIRS += /opt/local/lib + LDFLAGS += -framework CoreFoundation -framework IOKit +endif + +LIB_NAME = DummyDevice + +LIB_DIRS = ../../../ThirdParty/PSCommon/XnLib/Bin/$(PLATFORM)-$(CFG) +USED_LIBS = XnLib dl pthread +ifneq ("$(OSTYPE)","Darwin") + USED_LIBS += rt +endif + +CFLAGS += -Wall + +OUT_DIR := $(OUT_DIR)/OpenNI2/Drivers + +include ../../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Source/Drivers/Kinect/BaseKinectStream.cpp b/Source/Drivers/Kinect/BaseKinectStream.cpp new file mode 100644 index 0000000..93f8175 --- /dev/null +++ b/Source/Drivers/Kinect/BaseKinectStream.cpp @@ -0,0 +1,179 @@ +#include "BaseKinectStream.h" +#include "KinectStreamImpl.h" +#include +#include "XnHash.h" +#include "XnEvent.h" +#include "XnPlatform.h" +#include "NuiApi.h" +#include "PS1080.h" +#include "XnMath.h" + +using namespace oni::driver; +using namespace kinect_device; + +BaseKinectStream::BaseKinectStream(KinectStreamImpl* pStreamImpl): + m_pStreamImpl(pStreamImpl) +{ + m_running = false; + m_cropping.enabled = FALSE; + pStreamImpl->addStream(this); +} + +BaseKinectStream::~BaseKinectStream() +{ + destroy(); +} + +OniStatus BaseKinectStream::start() +{ + OniStatus status = m_pStreamImpl->start(); + if (status == ONI_STATUS_OK) + m_running = TRUE; + return status; +} + +void BaseKinectStream::stop() +{ + m_running = FALSE; + m_pStreamImpl->stop(); +} + +void BaseKinectStream::destroy() +{ + stop(); + m_pStreamImpl->removeStream(this); +} + +OniStatus BaseKinectStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + OniStatus status = ONI_STATUS_NOT_SUPPORTED; + switch (propertyId) + { + case ONI_STREAM_PROPERTY_CROPPING: + if (*pDataSize != sizeof(OniCropping)) + { + printf("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniCropping)); + status = ONI_STATUS_ERROR; + } + else + { + status = GetCropping((OniCropping*)data); + } + break; + case ONI_STREAM_PROPERTY_HORIZONTAL_FOV: + { + float* val = (float*)data; + XnDouble tmp; + if (m_videoMode.resolutionX == 640) + tmp = NUI_CAMERA_COLOR_NOMINAL_HORIZONTAL_FOV * xnl::Math::DTR; + else + tmp = NUI_CAMERA_DEPTH_NOMINAL_HORIZONTAL_FOV * xnl::Math::DTR; + *val = (float)tmp; + status = ONI_STATUS_OK; + break; + } + case ONI_STREAM_PROPERTY_VERTICAL_FOV: + { + float* val = (float*)data; + XnDouble tmp; + if (m_videoMode.resolutionY == 480) + tmp = NUI_CAMERA_COLOR_NOMINAL_VERTICAL_FOV * xnl::Math::DTR; + else + tmp = NUI_CAMERA_DEPTH_NOMINAL_VERTICAL_FOV * xnl::Math::DTR; + *val = (float)tmp; + status = ONI_STATUS_OK; + break; + } + case ONI_STREAM_PROPERTY_VIDEO_MODE: + { + if (*pDataSize != sizeof(OniVideoMode)) + { + printf("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniVideoMode)); + status = ONI_STATUS_ERROR; + } + else + { + status = GetVideoMode((OniVideoMode*)data); + } + + break; + } + default: + status = ONI_STATUS_NOT_SUPPORTED; + break; + } + + return status; +} + +OniStatus BaseKinectStream::setProperty(int propertyId, const void* data, int dataSize) +{ + OniStatus status = ONI_STATUS_NOT_SUPPORTED; + if (propertyId == ONI_STREAM_PROPERTY_CROPPING) + { + if (dataSize != sizeof(OniCropping)) + { + printf("Unexpected size: %d != %d\n", dataSize, sizeof(OniCropping)); + status = ONI_STATUS_ERROR; + } + status = SetCropping((OniCropping*)data); + } + else if (propertyId == ONI_STREAM_PROPERTY_VIDEO_MODE) + { + if (dataSize != sizeof(OniVideoMode)) + { + printf("Unexpected size: %d != %d\n", dataSize, sizeof(OniVideoMode)); + status = ONI_STATUS_ERROR; + } + status = SetVideoMode((OniVideoMode*)data); + } + return status; +} + +OniBool BaseKinectStream::isPropertySupported(int propertyId) +{ + OniBool status = FALSE; + switch (propertyId) + { + case ONI_STREAM_PROPERTY_CROPPING: + case ONI_STREAM_PROPERTY_HORIZONTAL_FOV: + case ONI_STREAM_PROPERTY_VERTICAL_FOV: + case ONI_STREAM_PROPERTY_VIDEO_MODE: + status = TRUE; + break; + default: + status = FALSE; + break; + } + return status; +} + +OniStatus BaseKinectStream::SetVideoMode(OniVideoMode* videoMode) +{ + if (!m_pStreamImpl->isRunning()) + { + m_videoMode = *videoMode; + m_pStreamImpl->setVideoMode(videoMode); + return ONI_STATUS_OK; + } + + return ONI_STATUS_OUT_OF_FLOW; +} + +OniStatus BaseKinectStream::GetVideoMode(OniVideoMode* pVideoMode) +{ + *pVideoMode = m_videoMode; + return ONI_STATUS_OK; +} + +OniStatus BaseKinectStream::SetCropping(OniCropping* cropping) +{ + m_cropping = *cropping; + return ONI_STATUS_OK; +} + +OniStatus BaseKinectStream::GetCropping(OniCropping* cropping) +{ + *cropping = m_cropping; + return ONI_STATUS_OK; +} diff --git a/Source/Drivers/Kinect/BaseKinectStream.h b/Source/Drivers/Kinect/BaseKinectStream.h new file mode 100644 index 0000000..5b36760 --- /dev/null +++ b/Source/Drivers/Kinect/BaseKinectStream.h @@ -0,0 +1,63 @@ +#ifndef _BASE_KINECT_STREAM_H_ +#define _BASE_KINECT_STREAM_H_ + +#include "Driver\OniDriverAPI.h" +#include "XnLib.h" +#include +#include "NuiApi.h" + +namespace kinect_device { + +class KinectStreamImpl; + +static const int KINECT_RESOLUTION_X_80 = 80; +static const int KINECT_RESOLUTION_Y_60 = 60; +static const int KINECT_RESOLUTION_X_320 = 320; +static const int KINECT_RESOLUTION_Y_240 = 240; +static const int KINECT_RESOLUTION_X_640 = 640; +static const int KINECT_RESOLUTION_Y_480 = 480; +static const int KINECT_RESOLUTION_X_1280 = 1280; +static const int KINECT_RESOLUTION_Y_960 = 960; + +class BaseKinectStream : public oni::driver::StreamBase +{ +public: + BaseKinectStream(KinectStreamImpl* pStreamImpl); + + virtual ~BaseKinectStream(); + + virtual OniStatus start(); + + virtual void stop(); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + + virtual OniBool isPropertySupported(int propertyId); + + virtual OniStatus SetVideoMode(OniVideoMode* pVideoMode); + + virtual OniStatus GetVideoMode(OniVideoMode* pVideoMode); + + virtual OniStatus SetCropping(OniCropping* cropping); + + virtual OniStatus GetCropping(OniCropping* cropping); + + bool isRunning() { return m_running; } + + virtual void frameReceived(NUI_IMAGE_FRAME& imageFrame, NUI_LOCKED_RECT &LockedRect) = 0; + + +protected: + KinectStreamImpl *m_pStreamImpl; + OniVideoMode m_videoMode; + OniCropping m_cropping; + bool m_running; + +private: + void destroy(); + +}; +} // namespace kinect_device +#endif //_BASE_KINECT_STREAM_H_ diff --git a/Source/Drivers/Kinect/ColorKinectStream.cpp b/Source/Drivers/Kinect/ColorKinectStream.cpp new file mode 100644 index 0000000..d7255ea --- /dev/null +++ b/Source/Drivers/Kinect/ColorKinectStream.cpp @@ -0,0 +1,192 @@ +#include "ColorKinectStream.h" + +#include +#include "XnHash.h" +#include "XnEvent.h" +#include "XnPlatform.h" + +#include "KinectStreamImpl.h" + +#include "NuiApi.h" + +using namespace oni::driver; +using namespace kinect_device; +#define DEFAULT_FPS 30 +ColorKinectStream::ColorKinectStream(KinectStreamImpl* pStreamImpl): + BaseKinectStream(pStreamImpl) +{ + m_videoMode.pixelFormat = ONI_PIXEL_FORMAT_RGB888; + m_videoMode.fps = DEFAULT_FPS; + m_videoMode.resolutionX = KINECT_RESOLUTION_X_640; + m_videoMode.resolutionY = KINECT_RESOLUTION_Y_480; +} + +OniStatus ColorKinectStream::start() +{ + OniStatus status = ONI_STATUS_ERROR; + if (m_pStreamImpl->getSensorType() == ONI_SENSOR_COLOR || m_pStreamImpl->isRunning() == false) + { + m_pStreamImpl->setSensorType(ONI_SENSOR_COLOR); + status = m_pStreamImpl->start(); + if (status == ONI_STATUS_OK) + m_running = TRUE; + + } + return status; +} + +void ColorKinectStream::frameReceived(NUI_IMAGE_FRAME& imageFrame, NUI_LOCKED_RECT &LockedRect) +{ + OniFrame* pFrame = getServices().acquireFrame(); + if (m_videoMode.pixelFormat == ONI_PIXEL_FORMAT_RGB888) + { + struct Rgba { unsigned char b, g, r, a; }; + struct Rgb { unsigned char r, g, b; }; + if (!m_cropping.enabled) + { + Rgba* data_in = reinterpret_cast(LockedRect.pBits); + Rgb* data_out = reinterpret_cast(pFrame->data); + pFrame->dataSize = m_videoMode.resolutionY * m_videoMode.resolutionX * 3; + Rgba * data_in_end = data_in + (m_videoMode.resolutionY * m_videoMode.resolutionX); + while (data_in < data_in_end) + { + data_out->b = data_in->b; + data_out->r = data_in->r; + data_out->g = data_in->g; + ++data_in; + ++data_out; + } + pFrame->stride = m_videoMode.resolutionX * 3; + } + else + { + Rgba* data_in = reinterpret_cast(LockedRect.pBits); + Rgb* data_out = reinterpret_cast(pFrame->data); + pFrame->dataSize = m_cropping.height * m_cropping.width * 3; + int cropX = m_cropping.originX; + int cropY = m_cropping.originY; + while (cropY < m_cropping.originY + m_cropping.height) + { + while (cropX < m_cropping.originX + m_cropping.width) + { + Rgba* iter = data_in + (cropX + m_videoMode.resolutionX * cropY); + unsigned char c = iter->b; + data_out->b = c; + data_out->r = iter->r; + data_out->g = iter->g; + ++data_out; + ++cropX; + } + ++cropY; + cropX = m_cropping.originX; + } + pFrame->stride = m_cropping.width * 3; + } + } + else + { + if (!m_cropping.enabled) + { + xnOSMemCopy(pFrame->data, LockedRect.pBits, LockedRect.size); + pFrame->dataSize = LockedRect.size; + pFrame->stride = m_videoMode.resolutionX * 2; + } + else + { + unsigned short* data_in = reinterpret_cast(LockedRect.pBits); + unsigned short* data_out = reinterpret_cast(pFrame->data); + pFrame->dataSize = m_cropping.height * m_cropping.width * 2; + + int cropX = m_cropping.originX; + int cropY = m_cropping.originY; + while (cropY < m_cropping.originY + m_cropping.height) + { + while (cropX < m_cropping.originX + m_cropping.width) + { + unsigned short* iter = data_in + (cropX + m_videoMode.resolutionX * cropY); + *data_out = *iter; + ++data_out; + ++cropX; + } + cropY++; + cropX = m_cropping.originX; + } + pFrame->stride = m_cropping.width * 2; + + } + } + pFrame->videoMode.resolutionX = m_videoMode.resolutionX; + pFrame->videoMode.resolutionY = m_videoMode.resolutionY; + if (m_cropping.enabled) + { + pFrame->width = m_cropping.width; + pFrame->height = m_cropping.height; + pFrame->cropOriginX = m_cropping.originX; + pFrame->cropOriginY = m_cropping.originY; + pFrame->croppingEnabled = m_cropping.enabled; + } + else + { + pFrame->cropOriginX = 0; + pFrame->cropOriginY = 0; + pFrame->croppingEnabled = m_cropping.enabled; + pFrame->width = m_videoMode.resolutionX; + pFrame->height = m_videoMode.resolutionY; + } + pFrame->videoMode.pixelFormat = m_videoMode.pixelFormat; + pFrame->videoMode.fps = m_videoMode.fps; + pFrame->sensorType = ONI_SENSOR_COLOR; + pFrame->frameIndex = imageFrame.dwFrameNumber; + pFrame->timestamp = imageFrame.liTimeStamp.QuadPart*1000; + raiseNewFrame(pFrame); + getServices().releaseFrame(pFrame); +} + +OniStatus ColorKinectStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + OniStatus status = ONI_STATUS_NOT_IMPLEMENTED; + switch (propertyId) + { + case ONI_STREAM_PROPERTY_AUTO_WHITE_BALANCE: + status = m_pStreamImpl->getAutoWhitBalance((BOOL*)data); + break; + case ONI_STREAM_PROPERTY_AUTO_EXPOSURE: + status = m_pStreamImpl->getAutoExposure((BOOL*)data); + break; + default: + status = BaseKinectStream::getProperty(propertyId, data, pDataSize); + } + return status; +} + +OniStatus ColorKinectStream::setProperty(int propertyId, const void* data, int pDataSize) +{ + OniStatus status = ONI_STATUS_NOT_IMPLEMENTED; + switch (propertyId) + { + case ONI_STREAM_PROPERTY_AUTO_WHITE_BALANCE: + status = m_pStreamImpl->setAutoWhiteBalance(*((BOOL*)data)); + break; + case ONI_STREAM_PROPERTY_AUTO_EXPOSURE: + status = m_pStreamImpl->setAutoExposure(*((BOOL*)data)); + break; + default: + status = BaseKinectStream::setProperty(propertyId, data, pDataSize); + } + return status; +} + +OniBool ColorKinectStream::isPropertySupported(int propertyId) +{ + OniBool status = FALSE; + switch (propertyId) + { + case ONI_STREAM_PROPERTY_AUTO_WHITE_BALANCE: + case ONI_STREAM_PROPERTY_AUTO_EXPOSURE: + status = TRUE; + break; + default: + status = BaseKinectStream::isPropertySupported(propertyId); + } + return status; +} diff --git a/Source/Drivers/Kinect/ColorKinectStream.h b/Source/Drivers/Kinect/ColorKinectStream.h new file mode 100644 index 0000000..2a5a363 --- /dev/null +++ b/Source/Drivers/Kinect/ColorKinectStream.h @@ -0,0 +1,27 @@ +#ifndef _COLOR_KINECT_STREAM_H_ +#define _COLOR_KINECT_STREAM_H_ + +#include "BaseKinectStream.h" + +struct INuiSensor; +namespace kinect_device { + + +class ColorKinectStream : public BaseKinectStream +{ +public: + ColorKinectStream(KinectStreamImpl* pStreamImpl); + + virtual OniStatus start(); + + virtual void frameReceived(NUI_IMAGE_FRAME& imageFrame, NUI_LOCKED_RECT &LockedRect); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + + virtual OniStatus setProperty(int propertyId, const void* data, int pDataSize); + + virtual OniBool isPropertySupported(int propertyId); +}; +} // namespace kinect_device + +#endif //_COLOR_KINECT_STREAM_H_ diff --git a/Source/Drivers/Kinect/D2S.h.h b/Source/Drivers/Kinect/D2S.h.h new file mode 100644 index 0000000..4456f2a --- /dev/null +++ b/Source/Drivers/Kinect/D2S.h.h @@ -0,0 +1,1003 @@ +XnUInt16 D2S[] = { +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 3, 7, 10, 13, 17, +20, 23, 27, 30, 33, 36, 40, 43, 46, 49, +52, 55, 59, 62, 65, 68, 71, 74, 77, 80, +83, 86, 88, 91, 94, 97, 100, 103, 106, 108, +111, 114, 117, 119, 122, 125, 128, 130, 133, 136, +138, 141, 143, 146, 149, 151, 154, 156, 159, 161, +164, 166, 169, 171, 174, 176, 178, 181, 183, 186, +188, 190, 193, 195, 197, 200, 202, 204, 206, 209, +211, 213, 215, 218, 220, 222, 224, 226, 229, 231, +233, 235, 237, 239, 241, 243, 245, 247, 249, 252, +254, 256, 258, 260, 262, 264, 266, 267, 269, 271, +273, 275, 277, 279, 281, 283, 285, 287, 288, 290, +292, 294, 296, 298, 299, 301, 303, 305, 307, 308, +310, 312, 314, 315, 317, 319, 321, 322, 324, 326, +327, 329, 331, 332, 334, 336, 337, 339, 341, 342, +344, 345, 347, 349, 350, 352, 353, 355, 357, 358, +360, 361, 363, 364, 366, 367, 369, 370, 372, 373, +375, 376, 378, 379, 381, 382, 383, 385, 386, 388, +389, 391, 392, 393, 395, 396, 398, 399, 400, 402, +403, 404, 406, 407, 409, 410, 411, 413, 414, 415, +416, 418, 419, 420, 422, 423, 424, 426, 427, 428, +429, 431, 432, 433, 434, 436, 437, 438, 439, 441, +442, 443, 444, 445, 447, 448, 449, 450, 451, 452, +454, 455, 456, 457, 458, 459, 461, 462, 463, 464, +465, 466, 467, 468, 470, 471, 472, 473, 474, 475, +476, 477, 478, 479, 480, 482, 483, 484, 485, 486, +487, 488, 489, 490, 491, 492, 493, 494, 495, 496, +497, 498, 499, 500, 501, 502, 503, 504, 505, 506, +507, 508, 509, 510, 511, 512, 513, 514, 515, 516, +517, 518, 519, 520, 521, 521, 522, 523, 524, 525, +526, 527, 528, 529, 530, 531, 532, 532, 533, 534, +535, 536, 537, 538, 539, 540, 540, 541, 542, 543, +544, 545, 546, 546, 547, 548, 549, 550, 551, 551, +552, 553, 554, 555, 556, 556, 557, 558, 559, 560, +561, 561, 562, 563, 564, 565, 565, 566, 567, 568, +568, 569, 570, 571, 572, 572, 573, 574, 575, 575, +576, 577, 578, 578, 579, 580, 581, 581, 582, 583, +584, 584, 585, 586, 587, 587, 588, 589, 590, 590, +591, 592, 592, 593, 594, 594, 595, 596, 597, 597, +598, 599, 599, 600, 601, 601, 602, 603, 604, 604, +605, 606, 606, 607, 608, 608, 609, 610, 610, 611, +612, 612, 613, 614, 614, 615, 615, 616, 617, 617, +618, 619, 619, 620, 621, 621, 622, 622, 623, 624, +624, 625, 626, 626, 627, 627, 628, 629, 629, 630, +631, 631, 632, 632, 633, 634, 634, 635, 635, 636, +636, 637, 638, 638, 639, 639, 640, 641, 641, 642, +642, 643, 643, 644, 645, 645, 646, 646, 647, 647, +648, 649, 649, 650, 650, 651, 651, 652, 652, 653, +654, 654, 655, 655, 656, 656, 657, 657, 658, 658, +659, 659, 660, 661, 661, 662, 662, 663, 663, 664, +664, 665, 665, 666, 666, 667, 667, 668, 668, 669, +669, 670, 670, 671, 671, 672, 672, 673, 673, 674, +674, 675, 675, 676, 676, 677, 677, 678, 678, 679, +679, 680, 680, 681, 681, 682, 682, 683, 683, 684, +684, 685, 685, 685, 686, 686, 687, 687, 688, 688, +689, 689, 690, 690, 691, 691, 691, 692, 692, 693, +693, 694, 694, 695, 695, 696, 696, 696, 697, 697, +698, 698, 699, 699, 699, 700, 700, 701, 701, 702, +702, 703, 703, 703, 704, 704, 705, 705, 706, 706, +706, 707, 707, 708, 708, 708, 709, 709, 710, 710, +711, 711, 711, 712, 712, 713, 713, 713, 714, 714, +715, 715, 715, 716, 716, 717, 717, 717, 718, 718, +719, 719, 719, 720, 720, 721, 721, 721, 722, 722, +723, 723, 723, 724, 724, 724, 725, 725, 726, 726, +726, 727, 727, 727, 728, 728, 729, 729, 729, 730, +730, 730, 731, 731, 732, 732, 732, 733, 733, 733, +734, 734, 734, 735, 735, 736, 736, 736, 737, 737, +737, 738, 738, 738, 739, 739, 739, 740, 740, 741, +741, 741, 742, 742, 742, 743, 743, 743, 744, 744, +744, 745, 745, 745, 746, 746, 746, 747, 747, 747, +748, 748, 748, 749, 749, 749, 750, 750, 750, 751, +751, 751, 752, 752, 752, 753, 753, 753, 754, 754, +754, 755, 755, 755, 756, 756, 756, 756, 757, 757, +757, 758, 758, 758, 759, 759, 759, 760, 760, 760, +761, 761, 761, 761, 762, 762, 762, 763, 763, 763, +764, 764, 764, 765, 765, 765, 765, 766, 766, 766, +767, 767, 767, 768, 768, 768, 768, 769, 769, 769, +770, 770, 770, 770, 771, 771, 771, 772, 772, 772, +773, 773, 773, 773, 774, 774, 774, 775, 775, 775, +775, 776, 776, 776, 776, 777, 777, 777, 778, 778, +778, 778, 779, 779, 779, 780, 780, 780, 780, 781, +781, 781, 781, 782, 782, 782, 783, 783, 783, 783, +784, 784, 784, 784, 785, 785, 785, 785, 786, 786, +786, 787, 787, 787, 787, 788, 788, 788, 788, 789, +789, 789, 789, 790, 790, 790, 790, 791, 791, 791, +791, 792, 792, 792, 792, 793, 793, 793, 793, 794, +794, 794, 794, 795, 795, 795, 795, 796, 796, 796, +796, 797, 797, 797, 797, 798, 798, 798, 798, 799, +799, 799, 799, 800, 800, 800, 800, 801, 801, 801, +801, 801, 802, 802, 802, 802, 803, 803, 803, 803, +804, 804, 804, 804, 805, 805, 805, 805, 805, 806, +806, 806, 806, 807, 807, 807, 807, 808, 808, 808, +808, 808, 809, 809, 809, 809, 810, 810, 810, 810, +810, 811, 811, 811, 811, 812, 812, 812, 812, 812, +813, 813, 813, 813, 813, 814, 814, 814, 814, 815, +815, 815, 815, 815, 816, 816, 816, 816, 817, 817, +817, 817, 817, 818, 818, 818, 818, 818, 819, 819, +819, 819, 819, 820, 820, 820, 820, 820, 821, 821, +821, 821, 822, 822, 822, 822, 822, 823, 823, 823, +823, 823, 824, 824, 824, 824, 824, 825, 825, 825, +825, 825, 826, 826, 826, 826, 826, 827, 827, 827, +827, 827, 828, 828, 828, 828, 828, 828, 829, 829, +829, 829, 829, 830, 830, 830, 830, 830, 831, 831, +831, 831, 831, 832, 832, 832, 832, 832, 832, 833, +833, 833, 833, 833, 834, 834, 834, 834, 834, 835, +835, 835, 835, 835, 835, 836, 836, 836, 836, 836, +837, 837, 837, 837, 837, 837, 838, 838, 838, 838, +838, 839, 839, 839, 839, 839, 839, 840, 840, 840, +840, 840, 841, 841, 841, 841, 841, 841, 842, 842, +842, 842, 842, 842, 843, 843, 843, 843, 843, 843, +844, 844, 844, 844, 844, 845, 845, 845, 845, 845, +845, 846, 846, 846, 846, 846, 846, 847, 847, 847, +847, 847, 847, 848, 848, 848, 848, 848, 848, 849, +849, 849, 849, 849, 849, 850, 850, 850, 850, 850, +850, 850, 851, 851, 851, 851, 851, 851, 852, 852, +852, 852, 852, 852, 853, 853, 853, 853, 853, 853, +854, 854, 854, 854, 854, 854, 854, 855, 855, 855, +855, 855, 855, 856, 856, 856, 856, 856, 856, 857, +857, 857, 857, 857, 857, 857, 858, 858, 858, 858, +858, 858, 858, 859, 859, 859, 859, 859, 859, 860, +860, 860, 860, 860, 860, 860, 861, 861, 861, 861, +861, 861, 861, 862, 862, 862, 862, 862, 862, 863, +863, 863, 863, 863, 863, 863, 864, 864, 864, 864, +864, 864, 864, 865, 865, 865, 865, 865, 865, 865, +866, 866, 866, 866, 866, 866, 866, 867, 867, 867, +867, 867, 867, 867, 868, 868, 868, 868, 868, 868, +868, 868, 869, 869, 869, 869, 869, 869, 869, 870, +870, 870, 870, 870, 870, 870, 871, 871, 871, 871, +871, 871, 871, 871, 872, 872, 872, 872, 872, 872, +872, 873, 873, 873, 873, 873, 873, 873, 873, 874, +874, 874, 874, 874, 874, 874, 875, 875, 875, 875, +875, 875, 875, 875, 876, 876, 876, 876, 876, 876, +876, 876, 877, 877, 877, 877, 877, 877, 877, 878, +878, 878, 878, 878, 878, 878, 878, 879, 879, 879, +879, 879, 879, 879, 879, 880, 880, 880, 880, 880, +880, 880, 880, 881, 881, 881, 881, 881, 881, 881, +881, 882, 882, 882, 882, 882, 882, 882, 882, 882, +883, 883, 883, 883, 883, 883, 883, 883, 884, 884, +884, 884, 884, 884, 884, 884, 885, 885, 885, 885, +885, 885, 885, 885, 885, 886, 886, 886, 886, 886, +886, 886, 886, 887, 887, 887, 887, 887, 887, 887, +887, 887, 888, 888, 888, 888, 888, 888, 888, 888, +888, 889, 889, 889, 889, 889, 889, 889, 889, 890, +890, 890, 890, 890, 890, 890, 890, 890, 891, 891, +891, 891, 891, 891, 891, 891, 891, 892, 892, 892, +892, 892, 892, 892, 892, 892, 893, 893, 893, 893, +893, 893, 893, 893, 893, 893, 894, 894, 894, 894, +894, 894, 894, 894, 894, 895, 895, 895, 895, 895, +895, 895, 895, 895, 896, 896, 896, 896, 896, 896, +896, 896, 896, 896, 897, 897, 897, 897, 897, 897, +897, 897, 897, 898, 898, 898, 898, 898, 898, 898, +898, 898, 898, 899, 899, 899, 899, 899, 899, 899, +899, 899, 899, 900, 900, 900, 900, 900, 900, 900, +900, 900, 900, 901, 901, 901, 901, 901, 901, 901, +901, 901, 901, 902, 902, 902, 902, 902, 902, 902, +902, 902, 902, 903, 903, 903, 903, 903, 903, 903, +903, 903, 903, 904, 904, 904, 904, 904, 904, 904, +904, 904, 904, 905, 905, 905, 905, 905, 905, 905, +905, 905, 905, 905, 906, 906, 906, 906, 906, 906, +906, 906, 906, 906, 907, 907, 907, 907, 907, 907, +907, 907, 907, 907, 907, 908, 908, 908, 908, 908, +908, 908, 908, 908, 908, 908, 909, 909, 909, 909, +909, 909, 909, 909, 909, 909, 910, 910, 910, 910, +910, 910, 910, 910, 910, 910, 910, 911, 911, 911, +911, 911, 911, 911, 911, 911, 911, 911, 911, 912, +912, 912, 912, 912, 912, 912, 912, 912, 912, 912, +913, 913, 913, 913, 913, 913, 913, 913, 913, 913, +913, 914, 914, 914, 914, 914, 914, 914, 914, 914, +914, 914, 914, 915, 915, 915, 915, 915, 915, 915, +915, 915, 915, 915, 915, 916, 916, 916, 916, 916, +916, 916, 916, 916, 916, 916, 917, 917, 917, 917, +917, 917, 917, 917, 917, 917, 917, 917, 918, 918, +918, 918, 918, 918, 918, 918, 918, 918, 918, 918, +919, 919, 919, 919, 919, 919, 919, 919, 919, 919, +919, 919, 919, 920, 920, 920, 920, 920, 920, 920, +920, 920, 920, 920, 920, 921, 921, 921, 921, 921, +921, 921, 921, 921, 921, 921, 921, 921, 922, 922, +922, 922, 922, 922, 922, 922, 922, 922, 922, 922, +923, 923, 923, 923, 923, 923, 923, 923, 923, 923, +923, 923, 923, 924, 924, 924, 924, 924, 924, 924, +924, 924, 924, 924, 924, 924, 925, 925, 925, 925, +925, 925, 925, 925, 925, 925, 925, 925, 925, 926, +926, 926, 926, 926, 926, 926, 926, 926, 926, 926, +926, 926, 926, 927, 927, 927, 927, 927, 927, 927, +927, 927, 927, 927, 927, 927, 928, 928, 928, 928, +928, 928, 928, 928, 928, 928, 928, 928, 928, 928, +929, 929, 929, 929, 929, 929, 929, 929, 929, 929, +929, 929, 929, 929, 930, 930, 930, 930, 930, 930, +930, 930, 930, 930, 930, 930, 930, 930, 931, 931, +931, 931, 931, 931, 931, 931, 931, 931, 931, 931, +931, 931, 932, 932, 932, 932, 932, 932, 932, 932, +932, 932, 932, 932, 932, 932, 933, 933, 933, 933, +933, 933, 933, 933, 933, 933, 933, 933, 933, 933, +933, 934, 934, 934, 934, 934, 934, 934, 934, 934, +934, 934, 934, 934, 934, 934, 935, 935, 935, 935, +935, 935, 935, 935, 935, 935, 935, 935, 935, 935, +935, 936, 936, 936, 936, 936, 936, 936, 936, 936, +936, 936, 936, 936, 936, 936, 937, 937, 937, 937, +937, 937, 937, 937, 937, 937, 937, 937, 937, 937, +937, 938, 938, 938, 938, 938, 938, 938, 938, 938, +938, 938, 938, 938, 938, 938, 938, 939, 939, 939, +939, 939, 939, 939, 939, 939, 939, 939, 939, 939, +939, 939, 939, 940, 940, 940, 940, 940, 940, 940, +940, 940, 940, 940, 940, 940, 940, 940, 940, 941, +941, 941, 941, 941, 941, 941, 941, 941, 941, 941, +941, 941, 941, 941, 941, 942, 942, 942, 942, 942, +942, 942, 942, 942, 942, 942, 942, 942, 942, 942, +942, 943, 943, 943, 943, 943, 943, 943, 943, 943, +943, 943, 943, 943, 943, 943, 943, 943, 944, 944, +944, 944, 944, 944, 944, 944, 944, 944, 944, 944, +944, 944, 944, 944, 944, 945, 945, 945, 945, 945, +945, 945, 945, 945, 945, 945, 945, 945, 945, 945, +945, 945, 946, 946, 946, 946, 946, 946, 946, 946, +946, 946, 946, 946, 946, 946, 946, 946, 946, 946, +947, 947, 947, 947, 947, 947, 947, 947, 947, 947, +947, 947, 947, 947, 947, 947, 947, 948, 948, 948, +948, 948, 948, 948, 948, 948, 948, 948, 948, 948, +948, 948, 948, 948, 948, 949, 949, 949, 949, 949, +949, 949, 949, 949, 949, 949, 949, 949, 949, 949, +949, 949, 949, 950, 950, 950, 950, 950, 950, 950, +950, 950, 950, 950, 950, 950, 950, 950, 950, 950, +950, 950, 951, 951, 951, 951, 951, 951, 951, 951, +951, 951, 951, 951, 951, 951, 951, 951, 951, 951, +951, 952, 952, 952, 952, 952, 952, 952, 952, 952, +952, 952, 952, 952, 952, 952, 952, 952, 952, 952, +953, 953, 953, 953, 953, 953, 953, 953, 953, 953, +953, 953, 953, 953, 953, 953, 953, 953, 953, 954, +954, 954, 954, 954, 954, 954, 954, 954, 954, 954, +954, 954, 954, 954, 954, 954, 954, 954, 955, 955, +955, 955, 955, 955, 955, 955, 955, 955, 955, 955, +955, 955, 955, 955, 955, 955, 955, 955, 956, 956, +956, 956, 956, 956, 956, 956, 956, 956, 956, 956, +956, 956, 956, 956, 956, 956, 956, 956, 956, 957, +957, 957, 957, 957, 957, 957, 957, 957, 957, 957, +957, 957, 957, 957, 957, 957, 957, 957, 957, 958, +958, 958, 958, 958, 958, 958, 958, 958, 958, 958, +958, 958, 958, 958, 958, 958, 958, 958, 958, 958, +959, 959, 959, 959, 959, 959, 959, 959, 959, 959, +959, 959, 959, 959, 959, 959, 959, 959, 959, 959, +959, 960, 960, 960, 960, 960, 960, 960, 960, 960, +960, 960, 960, 960, 960, 960, 960, 960, 960, 960, +960, 960, 960, 961, 961, 961, 961, 961, 961, 961, +961, 961, 961, 961, 961, 961, 961, 961, 961, 961, +961, 961, 961, 961, 962, 962, 962, 962, 962, 962, +962, 962, 962, 962, 962, 962, 962, 962, 962, 962, +962, 962, 962, 962, 962, 962, 962, 963, 963, 963, +963, 963, 963, 963, 963, 963, 963, 963, 963, 963, +963, 963, 963, 963, 963, 963, 963, 963, 963, 964, +964, 964, 964, 964, 964, 964, 964, 964, 964, 964, +964, 964, 964, 964, 964, 964, 964, 964, 964, 964, +964, 964, 965, 965, 965, 965, 965, 965, 965, 965, +965, 965, 965, 965, 965, 965, 965, 965, 965, 965, +965, 965, 965, 965, 965, 966, 966, 966, 966, 966, +966, 966, 966, 966, 966, 966, 966, 966, 966, 966, +966, 966, 966, 966, 966, 966, 966, 966, 966, 967, +967, 967, 967, 967, 967, 967, 967, 967, 967, 967, +967, 967, 967, 967, 967, 967, 967, 967, 967, 967, +967, 967, 967, 968, 968, 968, 968, 968, 968, 968, +968, 968, 968, 968, 968, 968, 968, 968, 968, 968, +968, 968, 968, 968, 968, 968, 968, 968, 969, 969, +969, 969, 969, 969, 969, 969, 969, 969, 969, 969, +969, 969, 969, 969, 969, 969, 969, 969, 969, 969, +969, 969, 969, 970, 970, 970, 970, 970, 970, 970, +970, 970, 970, 970, 970, 970, 970, 970, 970, 970, +970, 970, 970, 970, 970, 970, 970, 970, 971, 971, +971, 971, 971, 971, 971, 971, 971, 971, 971, 971, +971, 971, 971, 971, 971, 971, 971, 971, 971, 971, +971, 971, 971, 971, 972, 972, 972, 972, 972, 972, +972, 972, 972, 972, 972, 972, 972, 972, 972, 972, +972, 972, 972, 972, 972, 972, 972, 972, 972, 972, +973, 973, 973, 973, 973, 973, 973, 973, 973, 973, +973, 973, 973, 973, 973, 973, 973, 973, 973, 973, +973, 973, 973, 973, 973, 973, 973, 974, 974, 974, +974, 974, 974, 974, 974, 974, 974, 974, 974, 974, +974, 974, 974, 974, 974, 974, 974, 974, 974, 974, +974, 974, 974, 974, 975, 975, 975, 975, 975, 975, +975, 975, 975, 975, 975, 975, 975, 975, 975, 975, +975, 975, 975, 975, 975, 975, 975, 975, 975, 975, +975, 975, 976, 976, 976, 976, 976, 976, 976, 976, +976, 976, 976, 976, 976, 976, 976, 976, 976, 976, +976, 976, 976, 976, 976, 976, 976, 976, 976, 976, +977, 977, 977, 977, 977, 977, 977, 977, 977, 977, +977, 977, 977, 977, 977, 977, 977, 977, 977, 977, +977, 977, 977, 977, 977, 977, 977, 977, 977, 978, +978, 978, 978, 978, 978, 978, 978, 978, 978, 978, +978, 978, 978, 978, 978, 978, 978, 978, 978, 978, +978, 978, 978, 978, 978, 978, 978, 978, 979, 979, +979, 979, 979, 979, 979, 979, 979, 979, 979, 979, +979, 979, 979, 979, 979, 979, 979, 979, 979, 979, +979, 979, 979, 979, 979, 979, 979, 979, 980, 980, +980, 980, 980, 980, 980, 980, 980, 980, 980, 980, +980, 980, 980, 980, 980, 980, 980, 980, 980, 980, +980, 980, 980, 980, 980, 980, 980, 980, 981, 981, +981, 981, 981, 981, 981, 981, 981, 981, 981, 981, +981, 981, 981, 981, 981, 981, 981, 981, 981, 981, +981, 981, 981, 981, 981, 981, 981, 981, 981, 982, +982, 982, 982, 982, 982, 982, 982, 982, 982, 982, +982, 982, 982, 982, 982, 982, 982, 982, 982, 982, +982, 982, 982, 982, 982, 982, 982, 982, 982, 982, +983, 983, 983, 983, 983, 983, 983, 983, 983, 983, +983, 983, 983, 983, 983, 983, 983, 983, 983, 983, +983, 983, 983, 983, 983, 983, 983, 983, 983, 983, +983, 983, 983, 984, 984, 984, 984, 984, 984, 984, +984, 984, 984, 984, 984, 984, 984, 984, 984, 984, +984, 984, 984, 984, 984, 984, 984, 984, 984, 984, +984, 984, 984, 984, 984, 985, 985, 985, 985, 985, +985, 985, 985, 985, 985, 985, 985, 985, 985, 985, +985, 985, 985, 985, 985, 985, 985, 985, 985, 985, +985, 985, 985, 985, 985, 985, 985, 985, 985, 986, +986, 986, 986, 986, 986, 986, 986, 986, 986, 986, +986, 986, 986, 986, 986, 986, 986, 986, 986, 986, +986, 986, 986, 986, 986, 986, 986, 986, 986, 986, +986, 986, 986, 987, 987, 987, 987, 987, 987, 987, +987, 987, 987, 987, 987, 987, 987, 987, 987, 987, +987, 987, 987, 987, 987, 987, 987, 987, 987, 987, +987, 987, 987, 987, 987, 987, 987, 987, 988, 988, +988, 988, 988, 988, 988, 988, 988, 988, 988, 988, +988, 988, 988, 988, 988, 988, 988, 988, 988, 988, +988, 988, 988, 988, 988, 988, 988, 988, 988, 988, +988, 988, 988, 989, 989, 989, 989, 989, 989, 989, +989, 989, 989, 989, 989, 989, 989, 989, 989, 989, +989, 989, 989, 989, 989, 989, 989, 989, 989, 989, +989, 989, 989, 989, 989, 989, 989, 989, 989, 990, +990, 990, 990, 990, 990, 990, 990, 990, 990, 990, +990, 990, 990, 990, 990, 990, 990, 990, 990, 990, +990, 990, 990, 990, 990, 990, 990, 990, 990, 990, +990, 990, 990, 990, 990, 990, 991, 991, 991, 991, +991, 991, 991, 991, 991, 991, 991, 991, 991, 991, +991, 991, 991, 991, 991, 991, 991, 991, 991, 991, +991, 991, 991, 991, 991, 991, 991, 991, 991, 991, +991, 991, 991, 991, 992, 992, 992, 992, 992, 992, +992, 992, 992, 992, 992, 992, 992, 992, 992, 992, +992, 992, 992, 992, 992, 992, 992, 992, 992, 992, +992, 992, 992, 992, 992, 992, 992, 992, 992, 992, +992, 992, 992, 993, 993, 993, 993, 993, 993, 993, +993, 993, 993, 993, 993, 993, 993, 993, 993, 993, +993, 993, 993, 993, 993, 993, 993, 993, 993, 993, +993, 993, 993, 993, 993, 993, 993, 993, 993, 993, +993, 993, 994, 994, 994, 994, 994, 994, 994, 994, +994, 994, 994, 994, 994, 994, 994, 994, 994, 994, +994, 994, 994, 994, 994, 994, 994, 994, 994, 994, +994, 994, 994, 994, 994, 994, 994, 994, 994, 994, +994, 994, 995, 995, 995, 995, 995, 995, 995, 995, +995, 995, 995, 995, 995, 995, 995, 995, 995, 995, +995, 995, 995, 995, 995, 995, 995, 995, 995, 995, +995, 995, 995, 995, 995, 995, 995, 995, 995, 995, +995, 995, 995, 995, 996, 996, 996, 996, 996, 996, +996, 996, 996, 996, 996, 996, 996, 996, 996, 996, +996, 996, 996, 996, 996, 996, 996, 996, 996, 996, +996, 996, 996, 996, 996, 996, 996, 996, 996, 996, +996, 996, 996, 996, 996, 996, 997, 997, 997, 997, +997, 997, 997, 997, 997, 997, 997, 997, 997, 997, +997, 997, 997, 997, 997, 997, 997, 997, 997, 997, +997, 997, 997, 997, 997, 997, 997, 997, 997, 997, +997, 997, 997, 997, 997, 997, 997, 997, 997, 998, +998, 998, 998, 998, 998, 998, 998, 998, 998, 998, +998, 998, 998, 998, 998, 998, 998, 998, 998, 998, +998, 998, 998, 998, 998, 998, 998, 998, 998, 998, +998, 998, 998, 998, 998, 998, 998, 998, 998, 998, +998, 998, 998, 999, 999, 999, 999, 999, 999, 999, +999, 999, 999, 999, 999, 999, 999, 999, 999, 999, +999, 999, 999, 999, 999, 999, 999, 999, 999, 999, +999, 999, 999, 999, 999, 999, 999, 999, 999, 999, +999, 999, 999, 999, 999, 999, 999, 999, 1000, 1000, +1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, +1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, +1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, +1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, +1000, 1000, 1000, 1000, 1001, 1001, 1001, 1001, 1001, 1001, +1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, +1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, +1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, +1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, +1001, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, +1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, +1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, +1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, +1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1003, +1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, +1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, +1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, +1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, +1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1004, +1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, +1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, +1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, +1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, +1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, 1004, +1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, +1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, +1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, +1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, +1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, +1005, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, +1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, +1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, +1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, +1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, +1006, 1006, 1006, 1006, 1006, 1007, 1007, 1007, 1007, 1007, +1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, +1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, +1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, +1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, +1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1007, 1008, +1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, +1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, +1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, +1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, +1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, +1008, 1008, 1008, 1008, 1008, 1009, 1009, 1009, 1009, 1009, +1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, +1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, +1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, +1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, +1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, 1009, +1009, 1009, 1009, 1010, 1010, 1010, 1010, 1010, 1010, 1010, +1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, +1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, +1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, +1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, +1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, +1010, 1010, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, +1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, +1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, +1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, +1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, +1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, 1011, +1011, 1011, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, +1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, +1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, +1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, +1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, +1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, +1012, 1012, 1012, 1012, 1012, 1013, 1013, 1013, 1013, 1013, +1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, +1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, +1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, +1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, +1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, +1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1014, 1014, +1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, +1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, +1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, +1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, +1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, +1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, 1014, +1014, 1014, 1014, 1014, 1015, 1015, 1015, 1015, 1015, 1015, +1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, +1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, +1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, +1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, +1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, +1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, +1015, 1015, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, +1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, +1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, +1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, +1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, +1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, +1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, +1016, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, +1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, +1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, +1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, +1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, +1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, +1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, 1017, +1017, 1017, 1017, 1018, 1018, 1018, 1018, 1018, 1018, 1018, +1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, +1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, +1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, +1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, +1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, +1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1018, +1018, 1018, 1018, 1018, 1018, 1018, 1019, 1019, 1019, 1019, +1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, +1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, +1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, +1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, +1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, +1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, +1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, +1019, 1019, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, 1020, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, 1021, +1021, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, +1022, 1022, 1022, 1022, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, +1026, 1026, 1026, 1026, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, +1027, 1027, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, +1028, 1028, 1028, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, +1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, 1030, +1030, 1030, 1030, 1030, 1030, 1030, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, +1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, +1032, 1032, 1032, 1032, 1032, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, +1033, 1033, 1033, 1033, 1033, 1033, 1033, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, +1034, 1034, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 1035, +1035, 1035, 1035, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, +1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, 1037, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, +1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, 1039, +1039, 1039, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, 1040, +1040, 1040, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, +1042, 1042, 1042, 1042, 1042, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, +1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1043, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, +1044, 1044, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, 1045, +1045, 1045, 1045, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, +1046, 1046, 1046, 1046, 1046, 1046, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, +1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, +1048, 1048, 1048, 1048, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, 1049, +1049, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, +1050, 1050, 1050, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1052, +1052}; + diff --git a/Source/Drivers/Kinect/DepthKinectStream.cpp b/Source/Drivers/Kinect/DepthKinectStream.cpp new file mode 100644 index 0000000..505c400 --- /dev/null +++ b/Source/Drivers/Kinect/DepthKinectStream.cpp @@ -0,0 +1,386 @@ +#include "DepthKinectStream.h" + +#include +#include "XnHash.h" +#include "XnEvent.h" +#include "XnPlatform.h" +#include "NuiApi.h" +#include "PS1080.h" +#include "D2S.h.h" +#include "S2D.h.h" +#include "KinectStreamImpl.h" + +using namespace oni::driver; +using namespace kinect_device; + + +static const XnInt MAX_SHIFT_VAL = 2047; +static const XnInt PARAM_COEFF_VAL = 4; +static const XnInt SHIFT_SCALE_VAL = 10; +static const XnInt GAIN_VAL = 42; +static const XnInt ZPD_VAL = 120; +static const XnInt CONST_SHIFT_VAL = 200; +static const XnInt DEVICE_MAX_DEPTH_VAL = 10000; +static const XnDouble ZPPS_VAL = 0.10520000010728836; +static const XnDouble LDDIS_VAL = 7.5; + +#define DEFAULT_FPS 30 + +DepthKinectStream::DepthKinectStream(KinectStreamImpl* pStreamImpl): + BaseKinectStream(pStreamImpl) +{ + m_videoMode.pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_videoMode.fps = DEFAULT_FPS; + m_videoMode.resolutionX = KINECT_RESOLUTION_X_640; + m_videoMode.resolutionY = KINECT_RESOLUTION_Y_480; +} + +// Discard the depth value equal or greater than the max value. +inline unsigned short filterReliableDepthValue(unsigned short value) +{ + return value < DEVICE_MAX_DEPTH_VAL ? value : 0; +} + +// Populate the image-related metadata (resolution, cropping, etc.) to OniDriverFrame. +void DepthKinectStream::populateFrameImageMetadata(OniFrame* pFrame, int dataUnitSize) +{ + if (!m_cropping.enabled) + { + pFrame->height = m_videoMode.resolutionY; + pFrame->width = m_videoMode.resolutionX; + pFrame->cropOriginX = pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + } else { + pFrame->height = m_cropping.height; + pFrame->width = m_cropping.width; + pFrame->cropOriginX = m_cropping.originX; + pFrame->cropOriginY = m_cropping.originY; + pFrame->croppingEnabled = TRUE; + } + pFrame->stride = pFrame->width * dataUnitSize; + pFrame->videoMode.resolutionY = m_videoMode.resolutionY; + pFrame->videoMode.resolutionX = m_videoMode.resolutionX; + pFrame->videoMode.pixelFormat = m_videoMode.pixelFormat; + pFrame->videoMode.fps = m_videoMode.fps; +} + +// FIXME: For preliminary benchmarking purpose. Should not belong here. +static void recordAverageProcessTime(const char* message, LARGE_INTEGER* pAccTime, LARGE_INTEGER* pAccCount, const LARGE_INTEGER& startTime) +{ +#if 0 // set to 1 to display the process time + LARGE_INTEGER endTime; + QueryPerformanceCounter(&endTime); + pAccTime->QuadPart += endTime.QuadPart - startTime.QuadPart; + pAccCount->QuadPart++; + printf("%s: %lld\n", message, pAccTime->QuadPart / pAccCount->QuadPart); +#endif +} + +// Copy the depth pixels (NUI_DEPTH_IMAGE_PIXEL) to OniDriverFrame +// with applying cropping but NO depth-to-image registration. +void DepthKinectStream::copyDepthPixelsStraight(const NUI_DEPTH_IMAGE_PIXEL* source, int numPoints, OniFrame* pFrame) +{ + // Note: The local variable assignments and const qualifiers are carefully designed to generate + // a high performance code with VC 2010. We recommend check the performance when changing the code + // even if it was a trivial change. + + // For benchmarking purpose + LARGE_INTEGER startTime; + QueryPerformanceCounter(&startTime); + + unsigned short* target = (unsigned short*) pFrame->data; + + const unsigned int width = pFrame->width; + const unsigned int height = pFrame->height; + const unsigned int skipWidth = m_videoMode.resolutionX - width; + + // Offset the starting position + source += pFrame->cropOriginX + pFrame->cropOriginY * m_videoMode.resolutionX; + + for (unsigned int y = 0; y < height; y++) + { + for (unsigned int x = 0; x < width; x++) + { + *(target++) = filterReliableDepthValue((source++)->depth); + } + source += skipWidth; + } + + // FIXME: for preliminary benchmarking purpose + static LARGE_INTEGER accTime, accCount; + recordAverageProcessTime("No Image Reg", &accTime, &accCount, startTime); +} + +// Copy the depth pixels (NUI_DEPTH_IMAGE_PIXEL) to OniDriverFrame +// with applying cropping and depth-to-image registration. +void DepthKinectStream::copyDepthPixelsWithImageRegistration(const NUI_DEPTH_IMAGE_PIXEL* source, int numPoints, OniFrame* pFrame) +{ + // Note: We evaluated another possible implementation using INuiCoordinateMapper*::MapColorFrameToDepthFrame, + // but, counterintuitively, it turned out to be slower than this implementation. We reverted it back. + + // Note: The local variable assignments and const qualifiers are carefully designed to generate + // a high performance code with VC 2010. We recommend check the performance when changing the code + // even if it was a trivial change. + + // For benchmarking purpose + LARGE_INTEGER startTime; + QueryPerformanceCounter(&startTime); + + NUI_IMAGE_RESOLUTION nuiResolution = + m_pStreamImpl->getNuiImagResolution(pFrame->videoMode.resolutionX, pFrame->videoMode.resolutionY); + + unsigned short* const target = (unsigned short*) pFrame->data; + xnOSMemSet(target, 0, pFrame->dataSize); + + m_depthValuesBuffer.SetSize(numPoints); + m_mappedCoordsBuffer.SetSize(numPoints * 2); + + // Pack depth data for NuiImageGetColorPixelCoordinateFrameFromDepthPixelFrameAtResolution + USHORT* depthValuesIter = m_depthValuesBuffer.GetData(); + for (int i = 0; i < numPoints; i++) { + *(depthValuesIter++) = (source + i)->depth << 3; + } + + // Need review: not sure if it is a good idea to directly invoke INuiSensore here. + m_pStreamImpl->getNuiSensor()->NuiImageGetColorPixelCoordinateFrameFromDepthPixelFrameAtResolution( + nuiResolution, // assume the target image with the same resolution as the source. + nuiResolution, + numPoints, + m_depthValuesBuffer.GetData(), + numPoints * 2, + m_mappedCoordsBuffer.GetData() + ); + + const unsigned int minX = pFrame->cropOriginX; + const unsigned int minY = pFrame->cropOriginY; + const unsigned int width = pFrame->width; + const unsigned int height = pFrame->height; + const LONG* mappedCoordsIter = m_mappedCoordsBuffer.GetData(); + + for (int i = 0; i < numPoints; i++) + { + const unsigned int x = *mappedCoordsIter++ - minX; + const unsigned int y = *mappedCoordsIter++ - minY; + if (x < width - 1 && y < height) { + const unsigned short d = filterReliableDepthValue((source+i)->depth); + unsigned short* p = target + x + y * width; + if (*p == 0 || *p > d) *p = d; + p++; + if (*p == 0 || *p > d) *p = d; + } + } + + // FIXME: for preliminary benchmarking purpose + static LARGE_INTEGER accTime, accCount; + recordAverageProcessTime("With Image Reg", &accTime, &accCount, startTime); +} + + +void DepthKinectStream::frameReceived(NUI_IMAGE_FRAME& imageFrame, NUI_LOCKED_RECT& LockedRect) +{ + OniFrame* pFrame = getServices().acquireFrame(); + + // populate the video-related metadata + populateFrameImageMetadata(pFrame, sizeof(unsigned short)); + + // populate other frame info + pFrame->sensorType = ONI_SENSOR_DEPTH; + pFrame->frameIndex = imageFrame.dwFrameNumber; + pFrame->timestamp = imageFrame.liTimeStamp.QuadPart*1000; + + // populate the pixel data + int numPoints = m_videoMode.resolutionY * m_videoMode.resolutionX; + if (m_pStreamImpl->getImageRegistrationMode() == ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR) { + copyDepthPixelsWithImageRegistration((NUI_DEPTH_IMAGE_PIXEL*)LockedRect.pBits, numPoints, pFrame); + } else { + copyDepthPixelsStraight((NUI_DEPTH_IMAGE_PIXEL*)LockedRect.pBits, numPoints, pFrame); + } + + raiseNewFrame(pFrame); + getServices().releaseFrame(pFrame); +} + +OniStatus DepthKinectStream::convertDepthToColorCoordinates(StreamBase* colorStream, int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY) +{ + return m_pStreamImpl->convertDepthToColorCoordinates(colorStream, depthX, depthY, depthZ, pColorX, pColorY); +} + +OniStatus DepthKinectStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + OniStatus status = ONI_STATUS_NOT_SUPPORTED; + switch (propertyId) + { + case ONI_STREAM_PROPERTY_MAX_VALUE: + { + XnInt * val = (XnInt *)data; + *val = DEVICE_MAX_DEPTH_VAL; + status = ONI_STATUS_OK; + break; + } + case ONI_STREAM_PROPERTY_MIRRORING: + { + XnBool * val = (XnBool *)data; + *val = TRUE; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_CLOSE_RANGE: + case XN_STREAM_PROPERTY_INPUT_FORMAT: + case XN_STREAM_PROPERTY_CROPPING_MODE: + case XN_STREAM_PROPERTY_PIXEL_REGISTRATION: + case XN_STREAM_PROPERTY_WHITE_BALANCE_ENABLED: + status = ONI_STATUS_NOT_SUPPORTED; + break; + case XN_STREAM_PROPERTY_GAIN: + { + XnInt* val = (XnInt*)data; + *val = GAIN_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_HOLE_FILTER: + case XN_STREAM_PROPERTY_REGISTRATION_TYPE: + case XN_STREAM_PROPERTY_AGC_BIN: + return ONI_STATUS_NOT_SUPPORTED; + case XN_STREAM_PROPERTY_CONST_SHIFT: + { + XnInt* val = (XnInt*)data; + *val = CONST_SHIFT_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR: + status = ONI_STATUS_NOT_SUPPORTED; + break; + case XN_STREAM_PROPERTY_MAX_SHIFT: + { + XnInt* val = (XnInt*)data; + *val = MAX_SHIFT_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_PARAM_COEFF: + { + XnInt* val = (XnInt*)data; + *val = PARAM_COEFF_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_SHIFT_SCALE: + { + XnInt* val = (XnInt*)data; + *val = SHIFT_SCALE_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE: + { + XnInt* val = (XnInt*)data; + *val = ZPD_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE: + { + XnDouble* val = (XnDouble*)data; + *val = ZPPS_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE: + { + XnDouble* val = (XnDouble*)data; + *val = LDDIS_VAL; + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_DCMOS_RCMOS_DISTANCE: + status = ONI_STATUS_NOT_SUPPORTED; + break; + case XN_STREAM_PROPERTY_S2D_TABLE: + { + *pDataSize = sizeof(S2D); + xnOSMemCopy(data, S2D, sizeof(S2D)); + status = ONI_STATUS_OK; + break; + } + case XN_STREAM_PROPERTY_D2S_TABLE: + { + *pDataSize = sizeof(D2S); + xnOSMemCopy(data, D2S, sizeof(D2S)); + status = ONI_STATUS_OK; + break; + } + default: + status = BaseKinectStream::getProperty(propertyId, data, pDataSize); + break; + } + + return status; +} + +OniBool DepthKinectStream::isPropertySupported(int propertyId) +{ + OniBool status = FALSE; + switch (propertyId) + { + case ONI_STREAM_PROPERTY_MAX_VALUE: + case ONI_STREAM_PROPERTY_MIRRORING: + case XN_STREAM_PROPERTY_GAIN: + case XN_STREAM_PROPERTY_CONST_SHIFT: + case XN_STREAM_PROPERTY_MAX_SHIFT: + case XN_STREAM_PROPERTY_PARAM_COEFF: + case XN_STREAM_PROPERTY_SHIFT_SCALE: + case XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE: + case XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE: + case XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE: + case XN_STREAM_PROPERTY_S2D_TABLE: + case XN_STREAM_PROPERTY_D2S_TABLE: + status = TRUE; + default: + status = BaseKinectStream::isPropertySupported(propertyId); + break; + } + return status; +} + +void DepthKinectStream::notifyAllProperties() +{ + XnDouble nDouble; + int size = sizeof(nDouble); + getProperty(XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, &nDouble, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, &nDouble, size); + + getProperty(XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, &nDouble, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, &nDouble, size); + + XnInt nInt; + size = sizeof(nInt); + getProperty(XN_STREAM_PROPERTY_GAIN, &nInt, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_GAIN, &nInt, size); + + getProperty(XN_STREAM_PROPERTY_CONST_SHIFT, &nInt, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_CONST_SHIFT, &nInt, size); + + getProperty(XN_STREAM_PROPERTY_MAX_SHIFT, &nInt, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_MAX_SHIFT, &nInt, size); + + getProperty(XN_STREAM_PROPERTY_SHIFT_SCALE, &nInt, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_SHIFT_SCALE, &nInt, size); + + getProperty(XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, &nInt, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, &nInt, size); + + getProperty(ONI_STREAM_PROPERTY_MAX_VALUE, &nInt, &size); + raisePropertyChanged(ONI_STREAM_PROPERTY_MAX_VALUE, &nInt, size); + + unsigned short nBuff[10001]; + size = sizeof(S2D); + getProperty(XN_STREAM_PROPERTY_S2D_TABLE, nBuff, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_S2D_TABLE, nBuff, size); + + size = sizeof(D2S); + getProperty(XN_STREAM_PROPERTY_D2S_TABLE, nBuff, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_D2S_TABLE, nBuff, size); + BaseKinectStream::notifyAllProperties(); +} diff --git a/Source/Drivers/Kinect/DepthKinectStream.h b/Source/Drivers/Kinect/DepthKinectStream.h new file mode 100644 index 0000000..3261635 --- /dev/null +++ b/Source/Drivers/Kinect/DepthKinectStream.h @@ -0,0 +1,34 @@ +#ifndef _DEPTH_KINECT_STREAM_H_ +#define _DEPTH_KINECT_STREAM_H_ + +#include "BaseKinectStream.h" +#include "XnArray.h" + +struct INuiSensor; +namespace kinect_device { + +class DepthKinectStream : public BaseKinectStream +{ +public: + DepthKinectStream(KinectStreamImpl* pStreamImpl); + + virtual void frameReceived(NUI_IMAGE_FRAME& imageFrame, NUI_LOCKED_RECT &LockedRect); + + virtual OniStatus convertDepthToColorCoordinates(StreamBase* colorStream, int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + + virtual OniBool isPropertySupported(int propertyId); + + virtual void notifyAllProperties(); + +private: + xnl::Array m_depthValuesBuffer; + xnl::Array m_mappedCoordsBuffer; + + void populateFrameImageMetadata(OniFrame* pFrame, int dataUnitSize); + void copyDepthPixelsStraight(const NUI_DEPTH_IMAGE_PIXEL* source, int numPoints, OniFrame* pFrame); + void copyDepthPixelsWithImageRegistration(const NUI_DEPTH_IMAGE_PIXEL* source, int numPoints, OniFrame* pFrame); +}; +} // namespace kinect_device +#endif //_DEPTH_KINECT_STREAM_H_ diff --git a/Source/Drivers/Kinect/IRKinectStream.cpp b/Source/Drivers/Kinect/IRKinectStream.cpp new file mode 100644 index 0000000..2aa43df --- /dev/null +++ b/Source/Drivers/Kinect/IRKinectStream.cpp @@ -0,0 +1,172 @@ +#include "IRKinectStream.h" + +#include +#include "XnHash.h" +#include "XnEvent.h" +#include "XnPlatform.h" +#include "KinectStreamImpl.h" +#include "NuiApi.h" + +using namespace oni::driver; +using namespace kinect_device; + +#define DEFAULT_FPS 30 + +IRKinectStream::IRKinectStream(KinectStreamImpl* pStreamImpl): + BaseKinectStream(pStreamImpl) +{ + m_videoMode.pixelFormat = ONI_PIXEL_FORMAT_GRAY8; + m_videoMode.fps = DEFAULT_FPS; + m_videoMode.resolutionX = KINECT_RESOLUTION_X_640; + m_videoMode.resolutionY = KINECT_RESOLUTION_Y_480; +} + +OniStatus IRKinectStream::start() +{ + OniStatus status = ONI_STATUS_ERROR; + if (m_pStreamImpl->getSensorType() == ONI_SENSOR_IR || m_pStreamImpl->isRunning() == false) + { + m_pStreamImpl->setSensorType(ONI_SENSOR_IR); + status = m_pStreamImpl->start(); + if (status == ONI_STATUS_OK) + m_running = TRUE; + + } + return status; +} + +void IRKinectStream::frameReceived(NUI_IMAGE_FRAME& imageFrame, NUI_LOCKED_RECT &LockedRect) +{ + OniFrame* pFrame = getServices().acquireFrame(); + if (m_videoMode.pixelFormat == ONI_PIXEL_FORMAT_GRAY16) + { + unsigned short* data_in = reinterpret_cast(LockedRect.pBits); + unsigned short* data_out = reinterpret_cast(pFrame->data); + if (!m_cropping.enabled) + { + unsigned short* data_in_end = data_in + (m_videoMode.resolutionY * m_videoMode.resolutionX); + while (data_in < data_in_end) + { + unsigned short intensity = (*data_in)>> 6; + *data_out = intensity; + ++data_in; + ++data_out; + } + pFrame->stride = m_videoMode.resolutionX * 2; + } + else + { + int cropY = m_cropping.originY; + while (cropY < m_cropping.originY + m_cropping.height) + { + int cropX = m_cropping.originX; + while (cropX < m_cropping.originX + m_cropping.width) + { + unsigned short *iter = data_in + (cropY * m_videoMode.resolutionX + cropX++); + *(data_out++) = (*iter) >> 6; + } + cropY++; + } + pFrame->stride = m_cropping.width * 2; + } + } + else if (m_videoMode.pixelFormat == ONI_PIXEL_FORMAT_GRAY8) + { + unsigned short* data_in = reinterpret_cast(LockedRect.pBits); + char* data_out = reinterpret_cast(pFrame->data); + if (!m_cropping.enabled) + { + unsigned short* data_in_end = data_in + (m_videoMode.resolutionY * m_videoMode.resolutionX); + while (data_in < data_in_end) + { + char intensity = (*data_in)>> 8; + (*data_out) = intensity; + ++data_in; + ++data_out; + } + pFrame->stride = m_videoMode.resolutionX; + } + else + { + int cropY = m_cropping.originY; + while (cropY < m_cropping.originY + m_cropping.height) + { + int cropX = m_cropping.originX; + while (cropX < m_cropping.originX + m_cropping.width) + { + unsigned short *iter = data_in + (cropY * m_videoMode.resolutionX + cropX++); + char intensity = (*iter) >> 8; + *(data_out++) = intensity; + } + cropY++; + } + pFrame->stride = m_cropping.width * 2; + + } + } + else + { + struct Rgb { unsigned char r, g, b; }; + unsigned short* data_in = reinterpret_cast(LockedRect.pBits); + Rgb* data_out = reinterpret_cast(pFrame->data); + if (!m_cropping.enabled) + { + unsigned short* data_in_end = data_in + (m_videoMode.resolutionY * m_videoMode.resolutionX); + while (data_in < data_in_end) + { + char intensity = (*data_in)>> 8; + data_out->b = intensity; + data_out->r = intensity; + data_out->g = intensity; + ++data_in; + ++data_out; + } + pFrame->stride = m_videoMode.resolutionX * 3; + } + else + { + int cropY = m_cropping.originY; + while (cropY < m_cropping.originY + m_cropping.height) + { + int cropX = m_cropping.originX; + while (cropX < m_cropping.originX + m_cropping.width) + { + unsigned short *iter = data_in + (cropY * m_videoMode.resolutionX + cropX++); + char intensity = (*iter) >> 8; + data_out->b = intensity; + data_out->r = intensity; + data_out++->g = intensity; + } + cropY++; + } + pFrame->stride = m_cropping.width * 3; + } + } + + if (!m_cropping.enabled) + { + pFrame->width = pFrame->videoMode.resolutionX = m_videoMode.resolutionX; + pFrame->height = pFrame->videoMode.resolutionY = m_videoMode.resolutionY; + pFrame->cropOriginX = pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + } + else + { + pFrame->width = m_cropping.width; + pFrame->videoMode.resolutionX = m_videoMode.resolutionX; + pFrame->height = m_cropping.height; + pFrame->videoMode.resolutionY = m_videoMode.resolutionY; + pFrame->cropOriginX = m_cropping.originX; + pFrame->cropOriginY = m_cropping.originY; + pFrame->croppingEnabled = TRUE; + } + pFrame->videoMode.pixelFormat = m_videoMode.pixelFormat; + pFrame->videoMode.fps = m_videoMode.fps; + pFrame->sensorType = ONI_SENSOR_IR; + pFrame->frameIndex = imageFrame.dwFrameNumber; + pFrame->timestamp = imageFrame.liTimeStamp.QuadPart*1000; + raiseNewFrame(pFrame); + getServices().releaseFrame(pFrame); +} + + diff --git a/Source/Drivers/Kinect/IRKinectStream.h b/Source/Drivers/Kinect/IRKinectStream.h new file mode 100644 index 0000000..3f93f0a --- /dev/null +++ b/Source/Drivers/Kinect/IRKinectStream.h @@ -0,0 +1,21 @@ +#ifndef _IR_KINECT_STREAM_H_ +#define _IR_KINECT_STREAM_H_ + +#include "BaseKinectStream.h" + +struct INuiSensor; +namespace kinect_device { + + +class IRKinectStream : public BaseKinectStream +{ +public: + IRKinectStream(KinectStreamImpl* pStreamImpl); + + virtual OniStatus start(); + + virtual void frameReceived(NUI_IMAGE_FRAME& imageFrame, NUI_LOCKED_RECT &LockedRect); + +}; +} // namespace kinect_device +#endif //_IR_KINECT_STREAM_H_ diff --git a/Source/Drivers/Kinect/Kinect.vcxproj b/Source/Drivers/Kinect/Kinect.vcxproj new file mode 100644 index 0000000..f54e8b2 --- /dev/null +++ b/Source/Drivers/Kinect/Kinect.vcxproj @@ -0,0 +1,198 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E636BACA-795F-41CF-BC52-14C727BF014E} + Kinect + + + + DynamicLibrary + true + Unicode + true + + + DynamicLibrary + true + Unicode + true + + + DynamicLibrary + false + true + MultiByte + true + + + DynamicLibrary + false + true + MultiByte + true + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include + $(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib; + true + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include; + $(VCInstallDir)lib\amd64;$(VCInstallDir)atlmfc\lib\amd64;$(WindowsSdkDir)lib\x64; + true + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include; + $(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib; + false + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include + false + + + + Level3 + Disabled + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;$(KINECTSDK10_DIR)\inc; + _WINDLL;%(PreprocessorDefinitions);_WINDOWS + false + true + + + true + Kinect10.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\;$(KINECTSDK10_DIR)\lib\x86; + true + + + + + Level3 + Disabled + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;$(KINECTSDK10_DIR)\inc; + _WINDLL;%(PreprocessorDefinitions);_WINDOWS + false + true + + + true + Kinect10.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\;$(KINECTSDK10_DIR)\lib\amd64; + + + + + true + + + + + Level3 + MaxSpeed + true + true + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;$(KINECTSDK10_DIR)\inc; + true + + + true + true + true + Kinect10.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\;$(KINECTSDK10_DIR)\lib\x86; + true + + + + + Level3 + MaxSpeed + true + true + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;$(KINECTSDK10_DIR)\inc; + true + + + true + true + true + Kinect10.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\;$(KINECTSDK10_DIR)\lib\amd64; + true + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\..\Include + ..\..\..\Include + ..\..\..\Include + ..\..\..\Include + + + + + + \ No newline at end of file diff --git a/Source/Drivers/Kinect/Kinect.vcxproj.filters b/Source/Drivers/Kinect/Kinect.vcxproj.filters new file mode 100644 index 0000000..c3f5c70 --- /dev/null +++ b/Source/Drivers/Kinect/Kinect.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + {aede41a2-2b14-4dfc-abfa-2561ae67c9f5} + + + + + Resource Files + + + \ No newline at end of file diff --git a/Source/Drivers/Kinect/KinectDevice.cpp b/Source/Drivers/Kinect/KinectDevice.cpp new file mode 100644 index 0000000..89247f7 --- /dev/null +++ b/Source/Drivers/Kinect/KinectDevice.cpp @@ -0,0 +1,187 @@ +#include "KinectDevice.h" +#include "DepthKinectStream.h" +#include "ColorKinectStream.h" +#include "IRKinectStream.h" +#include +#include "NuiApi.h" +#include "XnLog.h" + +using namespace kinect_device; +using namespace oni::driver; +#define DEFAULT_FPS 30 +KinectDevice::KinectDevice(INuiSensor * pNuiSensor):m_pDepthStream(NULL), m_pColorStream(NULL),m_pNuiSensor(pNuiSensor) +{ + m_numSensors = 3; + + m_sensors[0].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, 3); + m_sensors[0].sensorType = ONI_SENSOR_DEPTH; + m_sensors[0].numSupportedVideoModes = 3; + m_sensors[0].pSupportedVideoModes[0].pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_sensors[0].pSupportedVideoModes[0].fps = DEFAULT_FPS; + m_sensors[0].pSupportedVideoModes[0].resolutionX = 640; + m_sensors[0].pSupportedVideoModes[0].resolutionY = 480; + + m_sensors[0].pSupportedVideoModes[1].pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_sensors[0].pSupportedVideoModes[1].fps = DEFAULT_FPS; + m_sensors[0].pSupportedVideoModes[1].resolutionX = 320; + m_sensors[0].pSupportedVideoModes[1].resolutionY = 240; + + m_sensors[0].pSupportedVideoModes[2].pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_sensors[0].pSupportedVideoModes[2].fps = DEFAULT_FPS; + m_sensors[0].pSupportedVideoModes[2].resolutionX = 80; + m_sensors[0].pSupportedVideoModes[2].resolutionY = 60; + + m_sensors[1].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, 3); + m_sensors[1].sensorType = ONI_SENSOR_COLOR; + m_sensors[1].numSupportedVideoModes = 3; + m_sensors[1].pSupportedVideoModes[0].pixelFormat = ONI_PIXEL_FORMAT_RGB888; + m_sensors[1].pSupportedVideoModes[0].fps = 12; + m_sensors[1].pSupportedVideoModes[0].resolutionX = 1280; + m_sensors[1].pSupportedVideoModes[0].resolutionY = 960; + + m_sensors[1].pSupportedVideoModes[1].pixelFormat = ONI_PIXEL_FORMAT_RGB888; + m_sensors[1].pSupportedVideoModes[1].fps = DEFAULT_FPS; + m_sensors[1].pSupportedVideoModes[1].resolutionX = 640; + m_sensors[1].pSupportedVideoModes[1].resolutionY = 480; + + m_sensors[1].pSupportedVideoModes[2].pixelFormat = ONI_PIXEL_FORMAT_YUV422; + m_sensors[1].pSupportedVideoModes[2].fps = 15; + m_sensors[1].pSupportedVideoModes[2].resolutionX = 640; + m_sensors[1].pSupportedVideoModes[2].resolutionY = 480; + + m_sensors[2].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, 3); + m_sensors[2].sensorType = ONI_SENSOR_IR; + m_sensors[2].numSupportedVideoModes = 3; + m_sensors[2].pSupportedVideoModes[0].pixelFormat = ONI_PIXEL_FORMAT_RGB888; + m_sensors[2].pSupportedVideoModes[0].fps = DEFAULT_FPS; + m_sensors[2].pSupportedVideoModes[0].resolutionX = 640; + m_sensors[2].pSupportedVideoModes[0].resolutionY = 480; + + m_sensors[2].pSupportedVideoModes[1].pixelFormat = ONI_PIXEL_FORMAT_GRAY16; + m_sensors[2].pSupportedVideoModes[1].fps = DEFAULT_FPS; + m_sensors[2].pSupportedVideoModes[1].resolutionX = 640; + m_sensors[2].pSupportedVideoModes[1].resolutionY = 480; + + m_sensors[2].pSupportedVideoModes[2].pixelFormat = ONI_PIXEL_FORMAT_GRAY8; + m_sensors[2].pSupportedVideoModes[2].fps = DEFAULT_FPS; + m_sensors[2].pSupportedVideoModes[2].resolutionX = 640; + m_sensors[2].pSupportedVideoModes[2].resolutionY = 480; +} + +KinectDevice::~KinectDevice() +{ + if (m_pNuiSensor) + m_pNuiSensor->NuiShutdown(); + + if (m_pDepthStream != NULL) + XN_DELETE(m_pDepthStream); + + if (m_pColorStream!= NULL) + XN_DELETE(m_pColorStream); + + if (m_pNuiSensor) + m_pNuiSensor->Release(); +} + +OniStatus KinectDevice::getSensorInfoList(OniSensorInfo** pSensors, int* numSensors) +{ + *numSensors = m_numSensors; + *pSensors = m_sensors; + return ONI_STATUS_OK; +} + +StreamBase* KinectDevice::createStream(OniSensorType sensorType) +{ + BaseKinectStream* pImage = NULL; + + if (sensorType == ONI_SENSOR_COLOR ) + { + if (m_pColorStream == NULL) + { + m_pColorStream = XN_NEW(KinectStreamImpl, m_pNuiSensor, sensorType); + + } + pImage = XN_NEW(ColorKinectStream, m_pColorStream); + } + else if (sensorType == ONI_SENSOR_DEPTH ) + { + if (m_pDepthStream == NULL) + { + m_pDepthStream = XN_NEW(KinectStreamImpl, m_pNuiSensor, sensorType); + } + pImage = XN_NEW(DepthKinectStream, m_pDepthStream); + } + if (sensorType == ONI_SENSOR_IR ) + { + if (m_pColorStream == NULL) + { + m_pColorStream = XN_NEW(KinectStreamImpl, m_pNuiSensor, sensorType); + } + pImage = XN_NEW(IRKinectStream, m_pColorStream); + + } + return pImage; +} + +void kinect_device::KinectDevice::destroyStream(oni::driver::StreamBase* pStream) +{ + XN_DELETE(pStream); +} + +OniStatus KinectDevice::setProperty(int propertyId, const void* data, int dataSize) +{ + switch (propertyId) { + case ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION: + { + // FIXME data size validation + if (m_pDepthStream) { + OniImageRegistrationMode* pMode = (OniImageRegistrationMode*)data; + m_pDepthStream->setImageRegistrationMode(*pMode); + return ONI_STATUS_OK; + } else { + return ONI_STATUS_ERROR; + } + } + } + + return ONI_STATUS_NOT_IMPLEMENTED; +} + +OniStatus KinectDevice::getProperty(int propertyId, void* data, int* pDataSize) +{ + switch (propertyId) { + case ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION: + { + // FIXME data size validation + if (m_pDepthStream) { + OniImageRegistrationMode* pMode = (OniImageRegistrationMode*)data; + *pMode = m_pDepthStream->getImageRegistrationMode(); + return ONI_STATUS_OK; + } else { + return ONI_STATUS_ERROR; + } + } + } + + return ONI_STATUS_NOT_IMPLEMENTED; +} + +OniBool KinectDevice::isPropertySupported(int propertyId) +{ + return (propertyId == ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION); +} + +OniBool KinectDevice::isCommandSupported(int commandId) +{ + return ONI_STATUS_NOT_IMPLEMENTED; +} + +OniStatus KinectDevice::tryManualTrigger() +{ + return ONI_STATUS_NOT_IMPLEMENTED; +} + +OniBool KinectDevice::isImageRegistrationModeSupported(OniImageRegistrationMode mode) +{ + return (mode == ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR || mode == ONI_IMAGE_REGISTRATION_OFF); +} diff --git a/Source/Drivers/Kinect/KinectDevice.h b/Source/Drivers/Kinect/KinectDevice.h new file mode 100644 index 0000000..0fd2264 --- /dev/null +++ b/Source/Drivers/Kinect/KinectDevice.h @@ -0,0 +1,41 @@ +#ifndef _KINECT_DEVICE_H_ +#define _KINECT_DEVICE_H_ + +#include "Driver\OniDriverAPI.h" +#include "KinectStreamImpl.h" +#include "XnLib.h" +#include "XnHash.h" +#include "XnEvent.h" + +struct INuiSensor; + +namespace kinect_device { + +class KinectDevice : public oni::driver::DeviceBase +{ +public: + KinectDevice(INuiSensor * pNuiSensor); + virtual ~KinectDevice(); + + virtual OniStatus getSensorInfoList(OniSensorInfo** pSensors, int* numSources); + + virtual oni::driver::StreamBase* createStream(OniSensorType streamSource); + virtual void destroyStream(oni::driver::StreamBase* pStream); + + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniBool isPropertySupported(int propertyId); + virtual OniBool isCommandSupported(int commandId) ; + virtual OniStatus tryManualTrigger(); + + virtual OniBool isImageRegistrationModeSupported(OniImageRegistrationMode mode); + +private: + INuiSensor * m_pNuiSensor; + KinectStreamImpl* m_pDepthStream; + KinectStreamImpl* m_pColorStream; + int m_numSensors; + OniSensorInfo m_sensors[10]; +}; +} // namespace kinect_device +#endif //_KINECT_DRIVER_H_ \ No newline at end of file diff --git a/Source/Drivers/Kinect/KinectDriver.cpp b/Source/Drivers/Kinect/KinectDriver.cpp new file mode 100644 index 0000000..b70e736 --- /dev/null +++ b/Source/Drivers/Kinect/KinectDriver.cpp @@ -0,0 +1,219 @@ +#include "KinectDriver.h" +#include "KinectDevice.h" +#include +#include "NuiApi.h" +#include +#include "XnLog.h" + +using namespace oni::driver; +using namespace kinect_device; +static const char VENDOR_VAL[] = "Microsoft"; +static const char NAME_VAL[] = "Kinect"; +#define MICROSOFT_VENDOR_ID 0x045e +#define KINECT_FOR_WINDOWS_PRODUCT_ID 0x02bf + +KinectDriver::KinectDriver(OniDriverServices* pDriverServices) : DriverBase(pDriverServices) +{ + NuiSetDeviceStatusCallback( &(KinectDriver::StatusProc), this); +} + +KinectDriver::~KinectDriver() +{ + NuiSetDeviceStatusCallback(NULL, NULL); +} + +OniStatus KinectDriver::initialize(DeviceConnectedCallback connectedCallback, DeviceDisconnectedCallback disconnectedCallback, DeviceStateChangedCallback deviceStateChangedCallback, void* pCookie) +{ + HRESULT hr; + int iSensorCount = 0; + INuiSensor * pNuiSensor; + DriverBase::initialize(connectedCallback, disconnectedCallback, deviceStateChangedCallback, pCookie); + hr = NuiGetSensorCount(&iSensorCount); + if (FAILED(hr)) + { + return ONI_STATUS_OK; + } + + // Look at each Kinect sensor + for (int i = 0; i < iSensorCount; ++i) + { + // Create the sensor so we can check status, if we can't create it, move on to the next + hr = NuiCreateSensorByIndex(i, &pNuiSensor); + if (FAILED(hr)) + { + continue; + } + + // Get the status of the sensor, and if connected, then we can initialize it + hr = pNuiSensor->NuiStatus(); + OniDeviceInfo* pInfo = XN_NEW(OniDeviceInfo); + BSTR str = pNuiSensor->NuiDeviceConnectionId(); + size_t convertedChars = 0; + const size_t newsize = ONI_MAX_STR; + size_t origsize = wcslen(str) + 1; + wcstombs_s(&convertedChars, pInfo->uri, origsize, str, _TRUNCATE); + xnOSStrCopy(pInfo->vendor, VENDOR_VAL, ONI_MAX_STR); + xnOSStrCopy(pInfo->name, NAME_VAL, ONI_MAX_STR); + m_devices[pInfo] = NULL; + deviceConnected(pInfo); + deviceStateChanged(pInfo, hr); + + // This sensor wasn't OK, so release it since we're not using it + pNuiSensor->Release(); + } + return ONI_STATUS_OK; +} + +DeviceBase* KinectDriver::deviceOpen(const char* uri, const char* /*mode*/) +{ + for (xnl::Hash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (xnOSStrCmp(iter->Key()->uri, uri) == 0) + { + // Found + if (iter->Value() != NULL) + { + // already using + return iter->Value(); + } + else + { + INuiSensor * pNuiSensor; + HRESULT hr; + size_t convertedChars = 0; + wchar_t wcstring[ONI_MAX_STR]; + mbstowcs_s(&convertedChars, wcstring, ONI_MAX_STR, uri, _TRUNCATE); + // Create the sensor so we can check status, if we can't create it, move on to the next + hr = NuiCreateSensorById(wcstring, &pNuiSensor); + + if (FAILED(hr)) + { + return NULL; + } + + if (NULL != pNuiSensor) + { + // Initialize the Kinect and specify that we'll be using color + hr = pNuiSensor->NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH); + KinectDevice* pDevice = XN_NEW(KinectDevice, pNuiSensor); + if (pDevice == NULL) + { + return NULL; + } + iter->Value() = pDevice; + return pDevice; + } + + if (NULL == pNuiSensor || FAILED(hr)) + { + return NULL; + } + } + } + } + return NULL; +} + +void kinect_device::KinectDriver::deviceClose(oni::driver::DeviceBase* pDevice) +{ + for (xnl::Hash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (iter->Value() == pDevice) + { + iter->Value() = NULL; + XN_DELETE(pDevice); + return; + } + } + + // not our device?! + XN_ASSERT(FALSE); +} + +void KinectDriver::shutdown() +{ +} + +OniStatus KinectDriver::tryDevice(const char* uri) +{ + return ONI_STATUS_OK; +} + +void* KinectDriver::enableFrameSync(StreamBase** pStreams, int streamCount) +{ + return NULL; +} + +void KinectDriver::disableFrameSync(void* frameSyncGroup) +{ + +} + +void KinectDriver::StatusUpdate(const OLECHAR* instanceName, bool isConnected) +{ + char str[ONI_MAX_STR]; + size_t convertedChars = 0; + const size_t newsize = ONI_MAX_STR; + size_t origsize = wcslen(instanceName) + 1; + wcstombs_s(&convertedChars, str, origsize, instanceName, _TRUNCATE); + for (xnl::Hash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (xnOSStrCmp(iter->Key()->uri, str) == 0) + { + if (isConnected) + { + INuiSensor * pNuiSensor; + HRESULT hr = NuiCreateSensorById( instanceName, &pNuiSensor); + if (FAILED(hr)) + { + return; + } + // Get the status of the sensor, and if connected, then we can initialize it + hr = pNuiSensor->NuiStatus(); + deviceStateChanged(iter->Key(), (int)hr); + } + else + { + deviceDisconnected(iter->Key()); + KinectDevice* pDevice = (KinectDevice*)iter->Value(); + OniDeviceInfo* pInfo = (OniDeviceInfo*)iter->Key(); + m_devices.Remove(iter); + if (pDevice != NULL) + XN_DELETE(pDevice); + + XN_DELETE(pInfo); + } + return; + } + } + + if (isConnected) + { + INuiSensor * pNuiSensor; + HRESULT hr = NuiCreateSensorById( instanceName, &pNuiSensor); + if (FAILED(hr)) + { + return; + } + + // Get the status of the sensor, and if connected, then we can initialize it + hr = pNuiSensor->NuiStatus(); + OniDeviceInfo* pInfo = XN_NEW(OniDeviceInfo); + int index = pNuiSensor->NuiInstanceIndex(); + strcpy((char*)pInfo->uri, str); + xnOSStrCopy(pInfo->vendor, VENDOR_VAL, ONI_MAX_STR); + xnOSStrCopy(pInfo->name, NAME_VAL, ONI_MAX_STR); + pInfo->usbVendorId = MICROSOFT_VENDOR_ID; + pInfo->usbProductId = KINECT_FOR_WINDOWS_PRODUCT_ID; + m_devices[pInfo] = NULL; + deviceConnected(pInfo); + deviceStateChanged(pInfo, hr); + } +} + +void CALLBACK KinectDriver::StatusProc( HRESULT hrStatus, const OLECHAR* instanceName, const OLECHAR* uniqueDeviceName, void* pUserData ) +{ + ((KinectDriver*)pUserData)->StatusUpdate(instanceName,SUCCEEDED( hrStatus )); +} + +ONI_EXPORT_DRIVER(kinect_device::KinectDriver) \ No newline at end of file diff --git a/Source/Drivers/Kinect/KinectDriver.h b/Source/Drivers/Kinect/KinectDriver.h new file mode 100644 index 0000000..3d1b437 --- /dev/null +++ b/Source/Drivers/Kinect/KinectDriver.h @@ -0,0 +1,36 @@ +#ifndef _KINECT_DRIVER_H_ +#define _KINECT_DRIVER_H_ + +#include "Driver\OniDriverAPI.h" +#include "XnHash.h" +#include +#include "NuiApi.h" + +namespace kinect_device { + +class KinectDriver : public oni::driver::DriverBase +{ +public: + KinectDriver(OniDriverServices* pDriverServices); + + virtual OniStatus initialize(oni::driver::DeviceConnectedCallback connectedCallback, oni::driver::DeviceDisconnectedCallback disconnectedCallback, + oni::driver::DeviceStateChangedCallback deviceStateChangedCallback, void* pCookie); + + virtual ~KinectDriver(); + + virtual oni::driver::DeviceBase* deviceOpen(const char* uri, const char* mode); + virtual void deviceClose(oni::driver::DeviceBase* pDevice); + + virtual void shutdown(); + + virtual OniStatus tryDevice(const char* uri); + + virtual void* enableFrameSync(oni::driver::StreamBase** pStreams, int streamCount); + virtual void disableFrameSync(void* frameSyncGroup); + void StatusUpdate(const OLECHAR* instanceName, bool isConnected); + static void CALLBACK StatusProc( HRESULT hrStatus, const OLECHAR* instanceName, const OLECHAR* uniqueDeviceName , void* pUserData ); +private: + xnl::Hash m_devices; +}; +} // namespace kinect_device +#endif //_KINECT_DRIVER_H_ diff --git a/Source/Drivers/Kinect/KinectStreamImpl.cpp b/Source/Drivers/Kinect/KinectStreamImpl.cpp new file mode 100644 index 0000000..b911178 --- /dev/null +++ b/Source/Drivers/Kinect/KinectStreamImpl.cpp @@ -0,0 +1,319 @@ +#include "KinectStreamImpl.h" +#include "BaseKinectStream.h" + +#include "NuiApi.h" + +using namespace oni::driver; +using namespace kinect_device; +using namespace xnl; + +#define DEFAULT_FPS 30 + +KinectStreamImpl::KinectStreamImpl(INuiSensor *pNuiSensor, OniSensorType sensorType): + m_pNuiSensor(pNuiSensor), m_sensorType(sensorType), + m_running(FALSE), m_hStreamHandle(INVALID_HANDLE_VALUE), + m_hNextFrameEvent(CreateEvent(NULL, TRUE, FALSE, NULL)) + +{ + setDefaultVideoMode(); +} + +KinectStreamImpl::~KinectStreamImpl() +{ + if (m_running) + { + m_running = FALSE; + xnOSWaitForThreadExit(m_threadHandle, INFINITE); + xnOSCloseThread(&m_threadHandle); + } + + if (m_hNextFrameEvent != INVALID_HANDLE_VALUE) + CloseHandle(m_hNextFrameEvent); +} + +void KinectStreamImpl::addStream(BaseKinectStream* stream) +{ + m_streamList.AddLast(stream); +} + +void KinectStreamImpl::removeStream(BaseKinectStream* stream) +{ + m_streamList.Remove(stream); +} + +unsigned int KinectStreamImpl::getStreamCount() +{ + return m_streamList.Size(); +} + +void KinectStreamImpl::setVideoMode(OniVideoMode* videoMode) +{ + m_videoMode.fps = videoMode->fps; + m_videoMode.pixelFormat = videoMode->pixelFormat; + m_videoMode.resolutionX = videoMode->resolutionX; + m_videoMode.resolutionY = videoMode->resolutionY; +} + +OniStatus KinectStreamImpl::start() +{ + if (m_running != TRUE) + { + // Open a color image stream to receive frames + HRESULT hr = m_pNuiSensor->NuiImageStreamOpen( + getNuiImageType(), + getNuiImagResolution(m_videoMode.resolutionX, m_videoMode.resolutionY), + 0, + 2, + m_hNextFrameEvent, + &m_hStreamHandle); + + if (FAILED(hr)) + { + return ONI_STATUS_ERROR; + } + + //m_pNuiSensor->NuiImageStreamSetImageFrameFlags(m_pStreamHandle, NUI_IMAGE_STREAM_FLAG_ENABLE_NEAR_MODE); + + XnStatus nRetVal = xnOSCreateThread(threadFunc, this, &m_threadHandle); + if (nRetVal != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + return ONI_STATUS_OK; + } + else + { + return ONI_STATUS_OK; + } +} + +void KinectStreamImpl::stop() +{ + if (m_running == true) + { + List::Iterator iter = m_streamList.Begin(); + while( iter != m_streamList.End()) + { + if (((BaseKinectStream*)(*iter))->isRunning()) + return; + ++iter; + } + m_running = false; + xnOSWaitForThreadExit(m_threadHandle, INFINITE); + xnOSCloseThread(&m_threadHandle); + } +} + +void KinectStreamImpl::setSensorType(OniSensorType sensorType) +{ + if( m_sensorType != sensorType) + { + m_sensorType = sensorType; + setDefaultVideoMode(); + } +} + +static const unsigned int LOOP_TIMEOUT = 10; +void KinectStreamImpl::mainLoop() +{ + m_running = TRUE; + while (m_running) + { + if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextFrameEvent, LOOP_TIMEOUT) && m_running) + { + HRESULT hr; + NUI_IMAGE_FRAME imageFrame; + hr = m_pNuiSensor->NuiImageStreamGetNextFrame(m_hStreamHandle, 0, &imageFrame); + if (FAILED(hr)) + { + continue; + } + INuiFrameTexture * pTexture; + if (m_sensorType == ONI_SENSOR_DEPTH) + { + BOOL nearMode; + // Get the depth image pixel texture + hr = m_pNuiSensor->NuiImageFrameGetDepthImagePixelFrameTexture( + m_hStreamHandle, &imageFrame, &nearMode, &pTexture); + } + else + { + pTexture = imageFrame.pFrameTexture; + } + + NUI_LOCKED_RECT LockedRect; + + // Lock the frame data so the Kinect knows not to modify it while we're reading it + pTexture->LockRect(0, &LockedRect, NULL, 0); + if (LockedRect.Pitch != 0) + { + List::ConstIterator iter = m_streamList.Begin(); + while( iter != m_streamList.End()) + { + if (((BaseKinectStream*)(*iter))->isRunning()) + ((BaseKinectStream*)(*iter))->frameReceived(imageFrame, LockedRect); + ++iter; + } + } + // We're done with the texture so unlock it + pTexture->UnlockRect(0); + + // Release the frame + m_pNuiSensor->NuiImageStreamReleaseFrame(m_hStreamHandle, &imageFrame); + } + } + return; +} + +OniStatus KinectStreamImpl::setAutoWhiteBalance(BOOL val) +{ + INuiColorCameraSettings *pCameraSettings; + HRESULT hr = m_pNuiSensor->NuiGetColorCameraSettings(&pCameraSettings); + if (FAILED(hr)) + { + return ONI_STATUS_ERROR; + } + hr = pCameraSettings->SetAutoWhiteBalance(val); + OniStatus status = FAILED(hr)? ONI_STATUS_ERROR : ONI_STATUS_OK; + pCameraSettings->Release(); + return status; +} + +OniStatus KinectStreamImpl::getAutoWhitBalance(BOOL *val) +{ + INuiColorCameraSettings *pCameraSettings; + HRESULT hr = m_pNuiSensor->NuiGetColorCameraSettings(&pCameraSettings); + if (FAILED(hr)) + { + return ONI_STATUS_ERROR; + } + hr = pCameraSettings->GetAutoWhiteBalance(val); + OniStatus status = FAILED(hr) ? ONI_STATUS_ERROR : ONI_STATUS_OK; + pCameraSettings->Release(); + return status; +} + +OniStatus KinectStreamImpl::setAutoExposure(BOOL val) +{ + INuiColorCameraSettings *pCameraSettings; + HRESULT hr = m_pNuiSensor->NuiGetColorCameraSettings(&pCameraSettings); + if (FAILED(hr)) + { + return ONI_STATUS_ERROR; + } + hr = pCameraSettings->SetAutoExposure(val); + OniStatus status = FAILED(hr) ? ONI_STATUS_ERROR : ONI_STATUS_OK; + pCameraSettings->Release(); + return status; +} + +OniStatus KinectStreamImpl::getAutoExposure(BOOL *val) +{ + INuiColorCameraSettings *pCameraSettings; + HRESULT hr = m_pNuiSensor->NuiGetColorCameraSettings(&pCameraSettings); + if (FAILED(hr)) + { + return ONI_STATUS_ERROR; + } + hr = pCameraSettings->GetAutoExposure(val); + OniStatus status = FAILED(hr) ? ONI_STATUS_ERROR : ONI_STATUS_OK; + pCameraSettings->Release(); + return status; +} + +OniStatus KinectStreamImpl::convertDepthToColorCoordinates(StreamBase* colorStream, int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY) +{ + OniVideoMode videoMode; + int size = sizeof(videoMode); + if (ONI_STATUS_OK != colorStream->getProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, &size)) + return ONI_STATUS_ERROR; + HRESULT hr = m_pNuiSensor->NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolution( + getNuiImagResolution(videoMode.resolutionX, videoMode.resolutionY), + getNuiImagResolution(m_videoMode.resolutionX, m_videoMode.resolutionY), + NULL, depthX, depthY, depthZ << 3, (LONG*)pColorX, (LONG*)pColorY); + if (FAILED(hr)) + return ONI_STATUS_ERROR; + return ONI_STATUS_OK; +} + +void KinectStreamImpl::setDefaultVideoMode() +{ + switch (m_sensorType) + { + case ONI_SENSOR_COLOR: + m_videoMode.pixelFormat = ONI_PIXEL_FORMAT_RGB888; + m_videoMode.fps = DEFAULT_FPS; + m_videoMode.resolutionX = KINECT_RESOLUTION_X_640; + m_videoMode.resolutionY = KINECT_RESOLUTION_Y_480; + break; + + case ONI_SENSOR_DEPTH: + m_videoMode.pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_videoMode.fps = DEFAULT_FPS; + m_videoMode.resolutionX = KINECT_RESOLUTION_X_640; + m_videoMode.resolutionY = KINECT_RESOLUTION_Y_480; + break; + + case ONI_SENSOR_IR: + m_videoMode.pixelFormat = ONI_PIXEL_FORMAT_GRAY8; + m_videoMode.fps = DEFAULT_FPS; + m_videoMode.resolutionX = KINECT_RESOLUTION_X_640; + m_videoMode.resolutionY = KINECT_RESOLUTION_Y_480; + break; + default: + ; + } +} + +NUI_IMAGE_RESOLUTION KinectStreamImpl::getNuiImagResolution(int resolutionX, int resolutionY) +{ + NUI_IMAGE_RESOLUTION imgResolution = NUI_IMAGE_RESOLUTION_320x240; + if (resolutionX == KINECT_RESOLUTION_X_80 && resolutionY == KINECT_RESOLUTION_Y_60 ) + { + imgResolution = NUI_IMAGE_RESOLUTION_80x60; + } + else if (resolutionX == KINECT_RESOLUTION_X_320 && resolutionY == KINECT_RESOLUTION_Y_240 ) + { + imgResolution = NUI_IMAGE_RESOLUTION_320x240; + } + else if (resolutionX == KINECT_RESOLUTION_X_640 && resolutionY == KINECT_RESOLUTION_Y_480 ) + { + imgResolution = NUI_IMAGE_RESOLUTION_640x480; + } + else if (resolutionX == KINECT_RESOLUTION_X_1280 && resolutionY == KINECT_RESOLUTION_Y_960 ) + { + imgResolution = NUI_IMAGE_RESOLUTION_1280x960; + } + return imgResolution; +} + +NUI_IMAGE_TYPE KinectStreamImpl::getNuiImageType() +{ + NUI_IMAGE_TYPE imgType; + switch (m_sensorType) + { + case ONI_SENSOR_IR: + imgType = NUI_IMAGE_TYPE_COLOR_INFRARED; + break; + case ONI_SENSOR_COLOR: + if (m_videoMode.pixelFormat == ONI_PIXEL_FORMAT_YUV422) + imgType = NUI_IMAGE_TYPE_COLOR_RAW_YUV; + else + imgType = NUI_IMAGE_TYPE_COLOR; + break; + case ONI_SENSOR_DEPTH: + imgType = NUI_IMAGE_TYPE_DEPTH; + break; + default: + imgType = NUI_IMAGE_TYPE_COLOR; + break; + } + return imgType; +} + +XN_THREAD_PROC KinectStreamImpl::threadFunc(XN_THREAD_PARAM pThreadParam) +{ + KinectStreamImpl* pStream = (KinectStreamImpl*)pThreadParam; + pStream->mainLoop(); + XN_THREAD_PROC_RETURN(XN_STATUS_OK); +} diff --git a/Source/Drivers/Kinect/KinectStreamImpl.h b/Source/Drivers/Kinect/KinectStreamImpl.h new file mode 100644 index 0000000..de3cbdd --- /dev/null +++ b/Source/Drivers/Kinect/KinectStreamImpl.h @@ -0,0 +1,82 @@ +#ifndef _KINECT_STREAM_IMPL_H_ +#define _KINECT_STREAM_IMPL_H_ + +#include "BaseKinectStream.h" +#include +#include "NuiApi.h" +#include "XnList.h" + +struct INuiSensor; + +namespace kinect_device { +class KinectStreamImpl +{ +public: + KinectStreamImpl(INuiSensor * pNuiSensor, OniSensorType sensorType); + + virtual ~KinectStreamImpl(); + + void addStream(BaseKinectStream* stream); + + void removeStream(BaseKinectStream* stream); + + unsigned int getStreamCount(); + + void setVideoMode(OniVideoMode* videoMode); + + OniStatus virtual start(); + + void virtual stop(); + + bool isRunning() { return m_running; } + + OniSensorType getSensorType () { return m_sensorType; } + + void setSensorType(OniSensorType sensorType); + + void mainLoop(); + + OniStatus setAutoWhiteBalance(BOOL val); + + OniStatus getAutoWhitBalance(BOOL *val); + + OniStatus setAutoExposure(BOOL val); + + OniStatus getAutoExposure(BOOL *val); + + OniImageRegistrationMode getImageRegistrationMode() const { return m_imageRegistrationMode; } + + void setImageRegistrationMode(OniImageRegistrationMode mode) { m_imageRegistrationMode = mode; } + + OniStatus convertDepthToColorCoordinates(oni::driver::StreamBase* colorStream, + int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY); + + static NUI_IMAGE_RESOLUTION getNuiImagResolution(int resolutionX, int resolutionY); + + INuiSensor* getNuiSensor() { return m_pNuiSensor; } // Need review: not sure if it is a good idea to expose this. + +private: + + void setDefaultVideoMode(); + + NUI_IMAGE_TYPE getNuiImageType(); + + static XN_THREAD_PROC threadFunc(XN_THREAD_PARAM pThreadParam); + + xnl::List m_streamList; + + OniImageRegistrationMode m_imageRegistrationMode; + + // Thread + INuiSensor* m_pNuiSensor; + OniSensorType m_sensorType; + bool m_running; + HANDLE m_hStreamHandle; + HANDLE m_hNextFrameEvent; + OniVideoMode m_videoMode; + + XN_THREAD_HANDLE m_threadHandle; +}; +} // namespace kinect_device + +#endif //_KINECT_STREAM_IMPL_H_ diff --git a/Source/Drivers/Kinect/S2D.h.h b/Source/Drivers/Kinect/S2D.h.h new file mode 100644 index 0000000..efc53a0 --- /dev/null +++ b/Source/Drivers/Kinect/S2D.h.h @@ -0,0 +1,207 @@ +XnUInt16 S2D[] = { +0, 315, 315, 315, 316, 316, 316, 316, 317, 317, +317, 318, 318, 318, 319, 319, 319, 319, 320, 320, +320, 321, 321, 321, 322, 322, 322, 322, 323, 323, +323, 324, 324, 324, 325, 325, 325, 326, 326, 326, +326, 327, 327, 327, 328, 328, 328, 329, 329, 329, +330, 330, 330, 331, 331, 331, 332, 332, 332, 332, +333, 333, 333, 334, 334, 334, 335, 335, 335, 336, +336, 336, 337, 337, 337, 338, 338, 338, 339, 339, +339, 340, 340, 340, 341, 341, 341, 342, 342, 343, +343, 343, 344, 344, 344, 345, 345, 345, 346, 346, +346, 347, 347, 347, 348, 348, 348, 349, 349, 350, +350, 350, 351, 351, 351, 352, 352, 352, 353, 353, +354, 354, 354, 355, 355, 355, 356, 356, 356, 357, +357, 358, 358, 358, 359, 359, 359, 360, 360, 361, +361, 361, 362, 362, 363, 363, 363, 364, 364, 364, +365, 365, 366, 366, 366, 367, 367, 368, 368, 368, +369, 369, 370, 370, 370, 371, 371, 372, 372, 372, +373, 373, 374, 374, 374, 375, 375, 376, 376, 377, +377, 377, 378, 378, 379, 379, 379, 380, 380, 381, +381, 382, 382, 382, 383, 383, 384, 384, 385, 385, +385, 386, 386, 387, 387, 388, 388, 389, 389, 389, +390, 390, 391, 391, 392, 392, 393, 393, 393, 394, +394, 395, 395, 396, 396, 397, 397, 398, 398, 398, +399, 399, 400, 400, 401, 401, 402, 402, 403, 403, +404, 404, 405, 405, 406, 406, 407, 407, 408, 408, +409, 409, 409, 410, 410, 411, 411, 412, 412, 413, +413, 414, 414, 415, 415, 416, 416, 417, 418, 418, +419, 419, 420, 420, 421, 421, 422, 422, 423, 423, +424, 424, 425, 425, 426, 426, 427, 427, 428, 429, +429, 430, 430, 431, 431, 432, 432, 433, 433, 434, +435, 435, 436, 436, 437, 437, 438, 438, 439, 440, +440, 441, 441, 442, 442, 443, 444, 444, 445, 445, +446, 446, 447, 448, 448, 449, 449, 450, 451, 451, +452, 452, 453, 454, 454, 455, 455, 456, 457, 457, +458, 458, 459, 460, 460, 461, 462, 462, 463, 463, +464, 465, 465, 466, 467, 467, 468, 468, 469, 470, +470, 471, 472, 472, 473, 474, 474, 475, 476, 476, +477, 478, 478, 479, 480, 480, 481, 482, 482, 483, +484, 484, 485, 486, 487, 487, 488, 489, 489, 490, +491, 491, 492, 493, 494, 494, 495, 496, 496, 497, +498, 499, 499, 500, 501, 502, 502, 503, 504, 504, +505, 506, 507, 507, 508, 509, 510, 511, 511, 512, +513, 514, 514, 515, 516, 517, 517, 518, 519, 520, +521, 521, 522, 523, 524, 525, 525, 526, 527, 528, +529, 529, 530, 531, 532, 533, 534, 534, 535, 536, +537, 538, 539, 540, 540, 541, 542, 543, 544, 545, +546, 546, 547, 548, 549, 550, 551, 552, 553, 554, +554, 555, 556, 557, 558, 559, 560, 561, 562, 563, +564, 565, 565, 566, 567, 568, 569, 570, 571, 572, +573, 574, 575, 576, 577, 578, 579, 580, 581, 582, +583, 584, 585, 586, 587, 588, 589, 590, 591, 592, +593, 594, 595, 596, 597, 598, 599, 600, 601, 602, +603, 604, 606, 607, 608, 609, 610, 611, 612, 613, +614, 615, 616, 618, 619, 620, 621, 622, 623, 624, +625, 627, 628, 629, 630, 631, 632, 634, 635, 636, +637, 638, 640, 641, 642, 643, 644, 646, 647, 648, +649, 650, 652, 653, 654, 655, 657, 658, 659, 661, +662, 663, 664, 666, 667, 668, 670, 671, 672, 674, +675, 676, 678, 679, 680, 682, 683, 684, 686, 687, +688, 690, 691, 693, 694, 696, 697, 698, 700, 701, +703, 704, 706, 707, 708, 710, 711, 713, 714, 716, +717, 719, 720, 722, 723, 725, 727, 728, 730, 731, +733, 734, 736, 738, 739, 741, 742, 744, 746, 747, +749, 750, 752, 754, 755, 757, 759, 761, 762, 764, +766, 767, 769, 771, 773, 774, 776, 778, 780, 781, +783, 785, 787, 789, 790, 792, 794, 796, 798, 800, +802, 803, 805, 807, 809, 811, 813, 815, 817, 819, +821, 823, 825, 827, 829, 831, 833, 835, 837, 839, +841, 843, 845, 847, 849, 851, 854, 856, 858, 860, +862, 864, 867, 869, 871, 873, 875, 878, 880, 882, +885, 887, 889, 891, 894, 896, 898, 901, 903, 906, +908, 910, 913, 915, 918, 920, 923, 925, 928, 930, +933, 935, 938, 940, 943, 946, 948, 951, 954, 956, +959, 962, 964, 967, 970, 973, 975, 978, 981, 984, +987, 989, 992, 995, 998, 1001, 1004, 1007, 1010, 1013, +1016, 1019, 1022, 1025, 1028, 1031, 1034, 1038, 1041, 1044, +1047, 1050, 1054, 1057, 1060, 1063, 1067, 1070, 1073, 1077, +1080, 1084, 1087, 1090, 1094, 1097, 1101, 1105, 1108, 1112, +1115, 1119, 1123, 1126, 1130, 1134, 1138, 1141, 1145, 1149, +1153, 1157, 1161, 1165, 1169, 1173, 1177, 1181, 1185, 1189, +1193, 1197, 1202, 1206, 1210, 1214, 1219, 1223, 1227, 1232, +1236, 1241, 1245, 1250, 1255, 1259, 1264, 1268, 1273, 1278, +1283, 1288, 1292, 1297, 1302, 1307, 1312, 1317, 1322, 1328, +1333, 1338, 1343, 1349, 1354, 1359, 1365, 1370, 1376, 1381, +1387, 1392, 1398, 1404, 1410, 1415, 1421, 1427, 1433, 1439, +1445, 1452, 1458, 1464, 1470, 1477, 1483, 1489, 1496, 1503, +1509, 1516, 1523, 1529, 1536, 1543, 1550, 1557, 1564, 1572, +1579, 1586, 1594, 1601, 1609, 1616, 1624, 1632, 1639, 1647, +1655, 1663, 1671, 1680, 1688, 1696, 1705, 1713, 1722, 1731, +1739, 1748, 1757, 1766, 1776, 1785, 1794, 1804, 1813, 1823, +1833, 1843, 1853, 1863, 1873, 1883, 1894, 1904, 1915, 1926, +1936, 1947, 1959, 1970, 1981, 1993, 2005, 2016, 2028, 2040, +2053, 2065, 2078, 2090, 2103, 2116, 2129, 2143, 2156, 2170, +2184, 2198, 2212, 2226, 2241, 2256, 2271, 2286, 2301, 2317, +2333, 2349, 2365, 2381, 2398, 2415, 2432, 2450, 2467, 2485, +2503, 2522, 2541, 2560, 2579, 2598, 2618, 2639, 2659, 2680, +2701, 2723, 2744, 2767, 2789, 2812, 2835, 2859, 2883, 2908, +2933, 2958, 2984, 3010, 3037, 3064, 3092, 3120, 3149, 3178, +3208, 3238, 3269, 3300, 3333, 3365, 3399, 3433, 3468, 3503, +3539, 3576, 3614, 3653, 3692, 3732, 3774, 3816, 3859, 3903, +3948, 3994, 4041, 4089, 4139, 4190, 4241, 4295, 4349, 4405, +4463, 4522, 4582, 4645, 4708, 4774, 4842, 4911, 4983, 5056, +5132, 5210, 5291, 5374, 5460, 5548, 5640, 5734, 5832, 5933, +6038, 6146, 6259, 6375, 6497, 6622, 6753, 6889, 7030, 7178, +7332, 7492, 7660, 7835, 8019, 8212, 8413, 8626, 8849, 9084, +9331, 9593, 9870, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0}; + diff --git a/Source/Drivers/OniFile/Android.mk b/Source/Drivers/OniFile/Android.mk new file mode 100644 index 0000000..7cedeae --- /dev/null +++ b/Source/Drivers/OniFile/Android.mk @@ -0,0 +1,63 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Sources +MY_SRC_FILES := \ + $(LOCAL_PATH)/*.cpp \ + $(LOCAL_PATH)/Formats/*.cpp \ + $(LOCAL_PATH)/XnLibExtensions/*.cpp + +ifdef OPENNI2_ANDROID_NDK_BUILD + MY_SRC_FILES += $(LOCAL_PATH)/../../../ThirdParty/LibJPEG/*.c +endif + +MY_SRC_FILE_EXPANDED := $(wildcard $(MY_SRC_FILES)) +LOCAL_SRC_FILES := $(MY_SRC_FILE_EXPANDED:$(LOCAL_PATH)/%=%) + +# C/CPP Flags +LOCAL_CFLAGS += $(OPENNI2_CFLAGS) + +# Includes +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/. \ + $(LOCAL_PATH)/../../../Include \ + $(LOCAL_PATH)/../../../ThirdParty/PSCommon/XnLib/Include \ + $(LOCAL_PATH)/../../../ThirdParty/LibJPEG \ + $(LOCAL_PATH)/Formats + +ifdef OPENNI2_ANDROID_NDK_BUILD + LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../ThirdParty/LibJPEG +else + LOCAL_C_INCLUDES += external/jpeg +endif + +# Dependencies +LOCAL_STATIC_LIBRARIES := XnLib +LOCAL_SHARED_LIBRARIES := liblog + +ifdef OPENNI2_ANDROID_OS_BUILD + LOCAL_SHARED_LIBRARIES += libjpeg +else + LOCAL_LDLIBS += -llog +endif + +# Output +LOCAL_MODULE:= libOniFile + +include $(BUILD_SHARED_LIBRARY) diff --git a/Source/Drivers/OniFile/DataRecords.cpp b/Source/Drivers/OniFile/DataRecords.cpp new file mode 100644 index 0000000..bffc7e6 --- /dev/null +++ b/Source/Drivers/OniFile/DataRecords.cpp @@ -0,0 +1,1070 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "DataRecords.h" +#include + +// NOTE(oleksii): these headers used to define XN_MASK_OPEN_NI and XN_CODEC_NULL. +// #include +// #include + +#define XN_MASK_OPEN_NI "OpenNI2" +#define XN_CODEC_NULL XN_CODEC_ID('N', 'U', 'L', 'L') + +const RecordingHeader DEFAULT_RECORDING_HEADER = +{ + {'N','I','1','0'}, //Magic + {1, 0, 1, 0}, //Version + 0, //Global max timestamp + 0 //Max node id +}; + +const XnUInt32 Record::MAGIC = 0x0052494E; //It reads "NIR\0" + +Record::Record(XnUInt8* pData, XnSizeT nMaxSize, XnBool bUseOld32Header) : + m_pData(pData), + m_nReadOffset(0), + m_nMaxSize((XnUInt32)nMaxSize), + m_bUseOld32Header(bUseOld32Header), + HEADER_SIZE(bUseOld32Header ? HEADER_SIZE_old32 : HEADER_SIZE_current) +{ + XN_ASSERT(m_pData != NULL); + XN_ASSERT(m_nMaxSize >= HEADER_SIZE); + // NOTE(oleksii): We don't really want to nullify payload size and undo + // record position in the constructor, because the buffer might have valid + // prefetched data. If they want to nullify some fields during construction, + // I'd suggest to write a factory function like MakeNilRecord() or something + // like that. + // SetNodeID(INVALID_NODE_ID); + // SetPayloadSize(0); + // SetUndoRecordPos(0); +} + +Record::Record(const Record &other) : + m_pData(other.m_pData), + m_nReadOffset(other.m_nReadOffset), + m_nMaxSize(other.m_nMaxSize), + m_bUseOld32Header(other.m_bUseOld32Header), + HEADER_SIZE(other.HEADER_SIZE) +{ + //We don't set the header info here, cuz it was already set by the other record +} + +RecordType Record::GetType() const +{ + return (RecordType)m_pHeader->m_nRecordType; +} + +XnUInt32 Record::GetNodeID() const +{ + return m_pHeader->m_nNodeID; +} + +XnUInt32 Record::GetSize() const +{ + return m_pHeader->m_nFieldsSize; +} + +XnUInt32 Record::GetPayloadSize() const +{ + return m_pHeader->m_nPayloadSize; +} + +XnUInt64 Record::GetUndoRecordPos() const +{ + if (m_bUseOld32Header) + return ((Header_old32 *)m_pHeader)->m_nUndoRecordPos; + else + return m_pHeader->m_nUndoRecordPos; +} + +void Record::SetNodeID(XnUInt32 nNodeID) +{ + m_pHeader->m_nNodeID = nNodeID; +} + +void Record::SetPayloadSize(XnUInt32 nPayloadSize) +{ + m_pHeader->m_nPayloadSize = nPayloadSize; +} + +void Record::SetUndoRecordPos(XnUInt64 nUndoRecordPos) +{ + m_pHeader->m_nUndoRecordPos = nUndoRecordPos; +} + +XnUInt8* Record::GetData() +{ + return m_pData; +} + +const XnUInt8* Record::GetData() const +{ + return m_pData; +} + +void Record::SetData(XnUInt8* pData, XnUInt32 nMaxSize) +{ + m_pData = pData; + m_nMaxSize = nMaxSize; +} + +const XnUInt8* Record::GetWritePos() const +{ + return m_pData + m_pHeader->m_nFieldsSize; +} + +XnStatus Record::StartWrite(XnUInt32 nRecordType) +{ + XN_VALIDATE_INPUT_PTR(m_pData); + if (m_nMaxSize < HEADER_SIZE) + { + XN_LOG_ERROR_RETURN(XN_STATUS_INPUT_BUFFER_OVERFLOW, XN_MASK_OPEN_NI, "Record buffer too small"); + } + m_pHeader->m_nMagic = MAGIC; + m_pHeader->m_nRecordType = nRecordType; + m_pHeader->m_nFieldsSize = HEADER_SIZE; + return XN_STATUS_OK; +} + +XnStatus Record::Write(const void* pData, XnUInt32 nSize) +{ + XN_VALIDATE_INPUT_PTR(pData); + XnUInt32 nNewSize = m_pHeader->m_nFieldsSize + nSize; + if (nNewSize > m_nMaxSize) + { + XN_LOG_ERROR_RETURN(XN_STATUS_INPUT_BUFFER_OVERFLOW, XN_MASK_OPEN_NI, "Record buffer too small"); + } + + xnOSMemCopy(m_pData + m_pHeader->m_nFieldsSize, pData, nSize); + m_pHeader->m_nFieldsSize = nNewSize; + + return XN_STATUS_OK; +} + +XnStatus Record::WriteString(const XnChar* str) +{ + XN_VALIDATE_INPUT_PTR(str); + XnUInt32 nStrSize = (XnUInt32)strlen(str) + 1; //+1 for terminating '\0' + XnStatus nRetVal = Write(&nStrSize, sizeof(nStrSize)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(str, nStrSize); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus Record::FinishWrite() +{ + //Nothing to do here right now - we can add a tail if we like. + return XN_STATUS_OK; +} + +XnStatus Record::StartRead() +{ + m_nReadOffset = HEADER_SIZE; + return XN_STATUS_OK; +} + +XnStatus Record::Read(void* pDest, XnUInt32 nSize) const +{ + XN_VALIDATE_OUTPUT_PTR(pDest); + if (m_nReadOffset + nSize > m_nMaxSize) + { + XN_LOG_ERROR_RETURN(XN_STATUS_INPUT_BUFFER_OVERFLOW, XN_MASK_OPEN_NI, "Record buffer too small"); + } + + xnOSMemCopy(pDest, m_pData + m_nReadOffset, nSize); + m_nReadOffset += nSize; + + return XN_STATUS_OK; +} + +XnStatus Record::ReadString(const XnChar* &strDest) const +{ + XnUInt32 nStrSize = 0; + //Get size + XnStatus nRetVal = Read(&nStrSize, sizeof(nStrSize)); + XN_IS_STATUS_OK(nRetVal); + //Check size is ok + if (m_nReadOffset + nStrSize > m_nMaxSize) + { + XN_LOG_ERROR_RETURN(XN_STATUS_INPUT_BUFFER_OVERFLOW, XN_MASK_OPEN_NI, "Record buffer too small"); + } + //Point destination string to current position + strDest = (const XnChar*)(m_pData + m_nReadOffset); + //Skip string + m_nReadOffset += nStrSize; + + return XN_STATUS_OK; +} + +const XnUInt8* Record::GetReadPos() const +{ + return (m_pData + m_nReadOffset); +} + +const XnUInt8* Record::GetPayload() const +{ + XN_ASSERT(m_pHeader->m_nPayloadSize != 0); + return (m_pData + m_pHeader->m_nFieldsSize); +} + +XnUInt8* Record::GetPayload() +{ + XN_ASSERT(m_pHeader->m_nPayloadSize != 0); + return (m_pData + m_pHeader->m_nFieldsSize); +} + +XnBool Record::IsHeaderValid() const +{ + if (m_pData == NULL) + { + return FALSE; + } + + if (m_pHeader->m_nMagic != Record::MAGIC) + { + return FALSE; + } + + if (m_pHeader->m_nFieldsSize < HEADER_SIZE) + { + return FALSE; + } + + return TRUE; +} + + +void Record::ResetRead() +{ + m_nReadOffset = 0; +} + +XnStatus Record::FinishRead() +{ + //Nothing to do here right now - if we add a tail we can read it here. + return XN_STATUS_OK; +} + +XnStatus Record::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + return xnOSStrFormat(strDest, nSize, &nCharsWritten, + "type=%u ID=%u fieldsSize=%u payloadSize=%u undoRecordPos=%u", + m_pHeader->m_nRecordType, m_pHeader->m_nNodeID, m_pHeader->m_nFieldsSize, + m_pHeader->m_nPayloadSize, m_pHeader->m_nFieldsSize); +} + +/****************************/ +/* NodeAdded_1_0_0_4_Record */ +/****************************/ +NodeAdded_1_0_0_4_Record::NodeAdded_1_0_0_4_Record(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + Record(pData, nMaxSize, bUseOld32Header), m_strNodeName(NULL), m_type(XnProductionNodeType(0)), m_compression(XN_CODEC_NULL) +{ + xnOSMemSet(&m_compression, 0, sizeof(m_compression)); +} + +NodeAdded_1_0_0_4_Record::NodeAdded_1_0_0_4_Record(const Record& record) : + Record(record), m_strNodeName(NULL), m_type(XnProductionNodeType(0)), m_compression(XN_CODEC_NULL) +{ + +} + +void NodeAdded_1_0_0_4_Record::SetNodeName(const XnChar* strNodeName) +{ + m_strNodeName = strNodeName; +} + +void NodeAdded_1_0_0_4_Record::SetNodeType(XnProductionNodeType type) +{ + m_type = type; +} + +void NodeAdded_1_0_0_4_Record::SetCompression(XnCodecID compression) +{ + m_compression = compression; +} + +const XnChar* NodeAdded_1_0_0_4_Record::GetNodeName() const +{ + return m_strNodeName; +} + +XnProductionNodeType NodeAdded_1_0_0_4_Record::GetNodeType() const +{ + return m_type; +} + +XnCodecID NodeAdded_1_0_0_4_Record::GetCompression() const +{ + return m_compression; +} + +XnStatus NodeAdded_1_0_0_4_Record::Encode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartWrite(RECORD_NODE_ADDED_1_0_0_4); + XN_IS_STATUS_OK(nRetVal); + nRetVal = EncodeImpl(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = FinishWrite(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeAdded_1_0_0_4_Record::Decode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = DecodeImpl(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = FinishRead(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeAdded_1_0_0_4_Record::EncodeImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = WriteString(m_strNodeName); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_type, sizeof(m_type)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_compression, sizeof(m_compression)); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus NodeAdded_1_0_0_4_Record::DecodeImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = ReadString(m_strNodeName); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_type, sizeof(m_type)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_compression, sizeof(m_compression)); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus NodeAdded_1_0_0_4_Record::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " name='%s' nodeType=%u compression='%.4s'", m_strNodeName, m_type, &m_compression); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + + return XN_STATUS_OK; +} +/****************************/ +/* NodeAdded_1_0_0_5_Record */ +/****************************/ +NodeAdded_1_0_0_5_Record::NodeAdded_1_0_0_5_Record(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + NodeAdded_1_0_0_4_Record(pData, nMaxSize, bUseOld32Header), m_nNumberOfFrames(0), m_nMinTimestamp(0), m_nMaxTimestamp(0) +{ +} + +NodeAdded_1_0_0_5_Record::NodeAdded_1_0_0_5_Record(const Record& record) : + NodeAdded_1_0_0_4_Record(record), m_nNumberOfFrames(0), m_nMinTimestamp(0), m_nMaxTimestamp(0) +{ + +} + +void NodeAdded_1_0_0_5_Record::SetNumberOfFrames(XnUInt32 nNumberOfFrames) +{ + m_nNumberOfFrames = nNumberOfFrames; +} + +void NodeAdded_1_0_0_5_Record::SetMinTimestamp(XnUInt64 nMinTimestamp) +{ + m_nMinTimestamp = nMinTimestamp; +} + +void NodeAdded_1_0_0_5_Record::SetMaxTimestamp(XnUInt64 nMaxTimestamp) +{ + m_nMaxTimestamp = nMaxTimestamp; +} + +XnUInt32 NodeAdded_1_0_0_5_Record::GetNumberOfFrames() const +{ + return m_nNumberOfFrames; +} + +XnUInt64 NodeAdded_1_0_0_5_Record::GetMinTimestamp() const +{ + return m_nMinTimestamp; +} + +XnUInt64 NodeAdded_1_0_0_5_Record::GetMaxTimestamp() const +{ + return m_nMaxTimestamp; +} + +XnStatus NodeAdded_1_0_0_5_Record::Encode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartWrite(RECORD_NODE_ADDED_1_0_0_5); + XN_IS_STATUS_OK(nRetVal); + nRetVal = EncodeImpl(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = FinishWrite(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeAdded_1_0_0_5_Record::Decode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = DecodeImpl(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = FinishRead(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeAdded_1_0_0_5_Record::EncodeImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = NodeAdded_1_0_0_4_Record::EncodeImpl(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_nNumberOfFrames, sizeof(m_nNumberOfFrames)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_nMinTimestamp, sizeof(m_nMinTimestamp)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_nMaxTimestamp, sizeof(m_nMaxTimestamp)); + XN_IS_STATUS_OK(nRetVal); + return (XN_STATUS_OK); +} + +XnStatus NodeAdded_1_0_0_5_Record::DecodeImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = NodeAdded_1_0_0_4_Record::DecodeImpl(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_nNumberOfFrames, sizeof(m_nNumberOfFrames)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_nMinTimestamp, sizeof(m_nMinTimestamp)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_nMaxTimestamp, sizeof(m_nMaxTimestamp)); + XN_IS_STATUS_OK(nRetVal); + return (XN_STATUS_OK); +} + +XnStatus NodeAdded_1_0_0_5_Record::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = NodeAdded_1_0_0_4_Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " numFrames=%u minTS=%u maxTS=%s", m_nNumberOfFrames, m_nMinTimestamp, m_nMaxTimestamp); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} + +/****************************/ +/* NodeAddedRecord */ +/****************************/ +NodeAddedRecord::NodeAddedRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + NodeAdded_1_0_0_5_Record(pData, nMaxSize, bUseOld32Header), m_nSeekTablePosition(0) +{ +} + +NodeAddedRecord::NodeAddedRecord(const Record& record) : + NodeAdded_1_0_0_5_Record(record), m_nSeekTablePosition(0) +{ +} + +void NodeAddedRecord::SetSeekTablePosition(XnUInt64 nPos) +{ + m_nSeekTablePosition = nPos; +} + +XnUInt64 NodeAddedRecord::GetSeekTablePosition() +{ + return m_nSeekTablePosition; +} + +XnStatus NodeAddedRecord::Encode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartWrite(RECORD_NODE_ADDED); + XN_IS_STATUS_OK(nRetVal); + nRetVal = NodeAdded_1_0_0_5_Record::EncodeImpl(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_nSeekTablePosition, sizeof(m_nSeekTablePosition)); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeAddedRecord::Decode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = NodeAdded_1_0_0_5_Record::DecodeImpl(); + XN_IS_STATUS_OK(nRetVal); + if (m_bUseOld32Header) + nRetVal = Read(&m_nSeekTablePosition, sizeof(XnUInt32)); + else + nRetVal = Read(&m_nSeekTablePosition, sizeof(m_nSeekTablePosition)); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeAddedRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = NodeAdded_1_0_0_5_Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " seekTablePos=%u", m_nSeekTablePosition); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} + +/*********************/ +/* NodeRemovedRecord */ +/*********************/ +NodeRemovedRecord::NodeRemovedRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + Record(pData, nMaxSize, bUseOld32Header) +{ +} + +NodeRemovedRecord::NodeRemovedRecord(const Record &record) : Record(record) +{ +} + +XnStatus NodeRemovedRecord::Encode() +{ + return StartWrite(RECORD_NODE_REMOVED); +} + +XnStatus NodeRemovedRecord::Decode() +{ + return StartRead(); +} + +XnStatus NodeRemovedRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} +/*********************/ +/* GeneralPropRecord */ +/*********************/ +GeneralPropRecord::GeneralPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header, XnUInt32 nPropRecordType /*= RECORD_GENERAL_PROPERTY*/) : + Record(pData, nMaxSize, bUseOld32Header), + m_nPropRecordType(nPropRecordType), + m_strPropName(NULL), + m_nPropDataSize(0), + m_pPropData(NULL) +{ +} + +GeneralPropRecord::GeneralPropRecord(const Record& record) : + Record(record), + m_nPropRecordType(RECORD_GENERAL_PROPERTY), + m_strPropName(NULL), + m_nPropDataSize(0), + m_pPropData(NULL) +{ +} + +void GeneralPropRecord::SetPropName(const XnChar* strPropName) +{ + m_strPropName = strPropName; +} + +void GeneralPropRecord::SetPropDataSize(XnUInt32 nPropDataSize) +{ + m_nPropDataSize = nPropDataSize; +} + +void GeneralPropRecord::SetPropData(const void* pPropData) +{ + m_pPropData = const_cast(pPropData); +} + +const XnChar* GeneralPropRecord::GetPropName() const +{ + return m_strPropName; +} + +XnUInt32 GeneralPropRecord::GetPropDataSize() const +{ + return m_nPropDataSize; +} + +const void* GeneralPropRecord::GetPropData() const +{ + return m_pPropData; +} + +XnStatus GeneralPropRecord::Encode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartWrite(m_nPropRecordType); + XN_IS_STATUS_OK(nRetVal); + nRetVal = WriteString(m_strPropName); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_nPropDataSize, sizeof(m_nPropDataSize)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(m_pPropData, m_nPropDataSize); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus GeneralPropRecord::Decode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = ReadString(m_strPropName); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_nPropDataSize, sizeof(m_nPropDataSize)); + XN_IS_STATUS_OK(nRetVal); + + //The property data is not copied but just pointed to + XnUInt8* pData = const_cast(GetReadPos()); + +#if (XN_PLATFORM == XN_PLATFORM_LINUX_ARM || XN_PLATFORM == XN_PLATFORM_ARC || XN_PLATFORM == XN_PLATFORM_ANDROID_ARM) + // under ARM we have some alignment issues. Move this buffer so it will be aligned. + XnUInt32 nAlignFix = XN_DEFAULT_MEM_ALIGN - ((XnUInt32)pData % XN_DEFAULT_MEM_ALIGN); + if (nAlignFix != 0) + { + xnOSMemMove(pData + nAlignFix, pData, m_nPropDataSize); + pData += nAlignFix; + } +#endif + + m_pPropData = pData; + + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus GeneralPropRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " propName='%s' propDataSize=%u", m_strPropName, m_nPropDataSize); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + + return XN_STATUS_OK; +} +/*****************/ +/* IntPropRecord */ +/*****************/ +IntPropRecord::IntPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + GeneralPropRecord(pData, nMaxSize, bUseOld32Header, RECORD_INT_PROPERTY), + m_nValue(0) +{ +} + +IntPropRecord::IntPropRecord(const Record &record) : + GeneralPropRecord(record), + m_nValue(0) +{ +} + +void IntPropRecord::SetValue(XnUInt64 nValue) +{ + m_nValue = nValue; + SetPropData(&m_nValue); + SetPropDataSize(sizeof(m_nValue)); +} + +XnUInt64 IntPropRecord::GetValue() const +{ + XN_ASSERT(GetPropDataSize() == sizeof(XnUInt64)); + return *(XnUInt64*)GetPropData(); +} + +XnStatus IntPropRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = GeneralPropRecord::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " val=%llu", GetValue()); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} +/******************/ +/* RealPropRecord */ +/******************/ +RealPropRecord::RealPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + GeneralPropRecord(pData, nMaxSize, bUseOld32Header, RECORD_REAL_PROPERTY), + m_dValue(0) +{ +} + +RealPropRecord::RealPropRecord(const Record &record) : + GeneralPropRecord(record), + m_dValue(0) +{ +} + +void RealPropRecord::SetValue(XnDouble dValue) +{ + m_dValue = dValue; + SetPropData(&m_dValue); + SetPropDataSize(sizeof(m_dValue)); +} + +XnDouble RealPropRecord::GetValue() const +{ + XN_ASSERT(GetPropDataSize() == sizeof(XnDouble)); + return *(XnDouble*)GetPropData(); +} + +XnStatus RealPropRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = GeneralPropRecord::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " val=%f", GetValue()); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} +/********************/ +/* StringPropRecord */ +/********************/ +StringPropRecord::StringPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + GeneralPropRecord(pData, nMaxSize, bUseOld32Header, RECORD_STRING_PROPERTY) +{ +} + +StringPropRecord::StringPropRecord(const Record &record) : GeneralPropRecord(record) +{ +} + +void StringPropRecord::SetValue(const XnChar* strValue) +{ + SetPropDataSize((XnUInt32)strlen(strValue)+1); + SetPropData(const_cast(strValue)); +} + +const XnChar* StringPropRecord::GetValue() const +{ + return (const XnChar*)GetPropData(); +} + +XnStatus StringPropRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = GeneralPropRecord::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " val='%s'", GetValue()); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} + +/***********************/ +/* NodeDataBeginRecord */ +/***********************/ +NodeDataBeginRecord::NodeDataBeginRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + Record(pData, nMaxSize, bUseOld32Header) +{ + xnOSMemSet(&m_seekInfo, 0, sizeof(m_seekInfo)); +} + +NodeDataBeginRecord::NodeDataBeginRecord(const Record& record) : Record(record) +{ + xnOSMemSet(&m_seekInfo, 0, sizeof(m_seekInfo)); +} + +XnUInt32 NodeDataBeginRecord::GetNumFrames() const +{ + return m_seekInfo.m_nFrames; +} + +XnUInt64 NodeDataBeginRecord::GetMaxTimeStamp() const +{ + return m_seekInfo.m_nMaxTimeStamp; +} + +XnStatus NodeDataBeginRecord::Encode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartWrite(RECORD_NODE_DATA_BEGIN); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_seekInfo, sizeof(m_seekInfo)); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeDataBeginRecord::Decode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_seekInfo, sizeof(m_seekInfo)); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeDataBeginRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " frames=%u maxTS=%u", m_seekInfo.m_nFrames, m_seekInfo.m_nMaxTimeStamp); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} + +/************************/ +/* NodeStateReadyRecord */ +/************************/ +NodeStateReadyRecord::NodeStateReadyRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + Record(pData, nMaxSize, bUseOld32Header) +{ + +} + +NodeStateReadyRecord::NodeStateReadyRecord(const Record& record) : + Record(record) +{ + +} + +XnStatus NodeStateReadyRecord::Encode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartWrite(RECORD_NODE_STATE_READY); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeStateReadyRecord::Decode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus NodeStateReadyRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +/***********************/ +/* NewDataRecordHeader */ +/***********************/ +NewDataRecordHeader::NewDataRecordHeader(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + Record(pData, nMaxSize, bUseOld32Header), + m_nTimeStamp(0), + m_nFrameNumber(0) +{ +} + +NewDataRecordHeader::NewDataRecordHeader(const Record &record) : + Record(record), + m_nTimeStamp(0), + m_nFrameNumber(0) +{ +} + +void NewDataRecordHeader::SetTimeStamp(XnUInt64 nTimeStamp) +{ + m_nTimeStamp = nTimeStamp; +} + +void NewDataRecordHeader::SetFrameNumber(XnUInt32 nFrameNumber) +{ + m_nFrameNumber = nFrameNumber; +} + +XnUInt64 NewDataRecordHeader::GetTimeStamp() const +{ + return m_nTimeStamp; +} + +XnUInt32 NewDataRecordHeader::GetFrameNumber() const +{ + return m_nFrameNumber; +} + + +XnStatus NewDataRecordHeader::Encode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartWrite(RECORD_NEW_DATA); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_nTimeStamp, sizeof(m_nTimeStamp)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Write(&m_nFrameNumber, sizeof(m_nFrameNumber)); + XN_IS_STATUS_OK(nRetVal); + //No call to FinishWrite() - this record is not done yet + return XN_STATUS_OK; +} + +XnStatus NewDataRecordHeader::Decode() +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_nTimeStamp, sizeof(m_nTimeStamp)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = Read(&m_nFrameNumber, sizeof(m_nFrameNumber)); + XN_IS_STATUS_OK(nRetVal); + //No call to FinishRead() - this record is not done yet + return XN_STATUS_OK; +} + +XnStatus NewDataRecordHeader::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + nRetVal = xnOSStrFormat(strDest + nCharsWritten, nSize - nCharsWritten, &nTempCharsWritten, + " TS=%llu FN=%u", m_nTimeStamp, m_nFrameNumber); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} + +/*******************/ +/* DataIndexRecordHeader */ +/*******************/ +DataIndexRecordHeader::DataIndexRecordHeader(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + Record(pData, nMaxSize, bUseOld32Header) +{ +} + +DataIndexRecordHeader::DataIndexRecordHeader(const Record& record) : + Record(record) +{ +} + +XnStatus DataIndexRecordHeader::Encode() +{ + XnStatus nRetVal = StartWrite(RECORD_SEEK_TABLE); + XN_IS_STATUS_OK(nRetVal); + //No call to FinishWrite() - this record is not done yet + return XN_STATUS_OK; +} + +XnStatus DataIndexRecordHeader::Decode() +{ + XnStatus nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + //No call to FinishRead() - this record is not done yet + return XN_STATUS_OK; +} + +XnStatus DataIndexRecordHeader::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + nCharsWritten += nTempCharsWritten; + return XN_STATUS_OK; +} + +/*************/ +/* EndRecord */ +/*************/ +EndRecord::EndRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header) : + Record(pData, nMaxSize, bUseOld32Header) +{ +} + +EndRecord::EndRecord(const Record& record) : Record(record) +{ +} + +XnStatus EndRecord::Encode() +{ + XnStatus nRetVal = StartWrite(RECORD_END); + XN_IS_STATUS_OK(nRetVal); + nRetVal = FinishWrite(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus EndRecord::Decode() +{ + XnStatus nRetVal = StartRead(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = FinishRead(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus EndRecord::AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten) +{ + XnUInt32 nTempCharsWritten = 0; + nCharsWritten = 0; + XnStatus nRetVal = Record::AsString(strDest, nSize, nTempCharsWritten); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} diff --git a/Source/Drivers/OniFile/DataRecords.h b/Source/Drivers/OniFile/DataRecords.h new file mode 100644 index 0000000..37056e6 --- /dev/null +++ b/Source/Drivers/OniFile/DataRecords.h @@ -0,0 +1,389 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __DATA_RECORDS_H__ +#define __DATA_RECORDS_H__ + +#include "XnPlatform.h" +#include "XnStatus.h" +#include "XnPlayerTypes.h" + +#pragma pack(push, 1) + +#define HEADER_MAGIC_SIZE 4 + +enum RecordType +{ + RECORD_NODE_ADDED_1_0_0_4 = 0x02, + RECORD_INT_PROPERTY = 0x03, + RECORD_REAL_PROPERTY = 0x04, + RECORD_STRING_PROPERTY = 0x05, + RECORD_GENERAL_PROPERTY = 0x06, + RECORD_NODE_REMOVED = 0x07, + RECORD_NODE_DATA_BEGIN = 0x08, + RECORD_NODE_STATE_READY = 0x09, + RECORD_NEW_DATA = 0x0A, + RECORD_END = 0x0B, + RECORD_NODE_ADDED_1_0_0_5 = 0x0C, + RECORD_NODE_ADDED = 0x0D, + RECORD_SEEK_TABLE = 0x0E, +}; + +#define INVALID_NODE_ID ((XnUInt32)-1) +#define INVALID_TIMESTAMP ((XnUInt64)-1) + +struct RecordingHeader +{ + XnChar headerMagic[HEADER_MAGIC_SIZE]; + XnVersion version; + XnUInt64 nGlobalMaxTimeStamp; + XnUInt32 nMaxNodeID; +}; + +extern const RecordingHeader DEFAULT_RECORDING_HEADER; + +class Record +{ +public: + Record(XnUInt8* pData, XnSizeT nMaxSize, XnBool bUseOld32Header); + Record(const Record &other); + RecordType GetType() const; + XnUInt32 GetNodeID() const; + XnUInt32 GetSize() const; //GetSize() returns just the fields' size, not including the payload + XnUInt32 GetPayloadSize() const; + XnUInt64 GetUndoRecordPos() const; + + void SetNodeID(XnUInt32 nNodeID); + void SetPayloadSize(XnUInt32 nPayloadSize); + void SetUndoRecordPos(XnUInt64 nUndoRecordPos); + + XnUInt8* GetData(); //GetData() returns the entire encoded record + const XnUInt8* GetData() const; //GetData() returns the entire encoded record + void SetData(XnUInt8* pData, XnUInt32 nMaxSize); + XnUInt8* GetPayload(); //GetPayload() returns just the payload part of the record (after the fields) + const XnUInt8* GetPayload() const; + const XnUInt8* GetReadPos() const; + XnBool IsHeaderValid() const; + void ResetRead(); + + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +protected: + const XnUInt8* GetWritePos() const; + XnStatus StartWrite(XnUInt32 nRecordType); + XnStatus Write(const void* pData, XnUInt32 nSize); + XnStatus WriteString(const XnChar* str); + XnStatus FinishWrite(); + + XnStatus StartRead(); + + //Copies the data to the specified output buffer + XnStatus Read(void* pData, XnUInt32 nSize) const; + + //Gives a pointer to the string in the current position, and advances that position till after the string. + XnStatus ReadString(const XnChar*& strDest) const; + XnStatus FinishRead(); + +private: + struct Header_old32 + { + XnUInt32 m_nMagic; + XnUInt32 m_nRecordType; + XnUInt32 m_nNodeID; + XnUInt32 m_nFieldsSize; + XnUInt32 m_nPayloadSize; + XnUInt32 m_nUndoRecordPos; + }; + struct Header + { + XnUInt32 m_nMagic; + XnUInt32 m_nRecordType; + XnUInt32 m_nNodeID; + XnUInt32 m_nFieldsSize; + XnUInt32 m_nPayloadSize; + XnUInt64 m_nUndoRecordPos; + }; + + union + { + Header* m_pHeader; + XnUInt8* m_pData; + }; + + static const XnUInt32 MAGIC; + mutable XnUInt32 m_nReadOffset; + XnUInt32 m_nMaxSize; +protected: + enum {HEADER_SIZE_current = sizeof(Header), + HEADER_SIZE_old32 = sizeof(Header_old32)}; + XnBool m_bUseOld32Header; + +public: + XnUInt32 HEADER_SIZE; +}; + +class NodeAdded_1_0_0_4_Record : public Record +{ +public: + NodeAdded_1_0_0_4_Record(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + NodeAdded_1_0_0_4_Record(const Record& record); + void SetNodeName(const XnChar* strNodeName); + void SetNodeType(XnProductionNodeType type); + void SetCompression(XnCodecID compression); + + const XnChar* GetNodeName() const; + XnProductionNodeType GetNodeType() const; + XnCodecID GetCompression() const; + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +protected: + XnStatus EncodeImpl(); + XnStatus DecodeImpl(); + +private: + const XnChar* m_strNodeName; + XnProductionNodeType m_type; + XnCodecID m_compression; +}; + +class NodeAdded_1_0_0_5_Record : public NodeAdded_1_0_0_4_Record +{ +public: + NodeAdded_1_0_0_5_Record(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + NodeAdded_1_0_0_5_Record(const Record& record); + + void SetNumberOfFrames(XnUInt32 nNumberOfFrames); + void SetMinTimestamp(XnUInt64 nMinTimestamp); + void SetMaxTimestamp(XnUInt64 nMaxTimestamp); + + XnUInt32 GetNumberOfFrames() const; + XnUInt64 GetMinTimestamp() const; + XnUInt64 GetMaxTimestamp() const; + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +protected: + XnStatus EncodeImpl(); + XnStatus DecodeImpl(); + +private: + XnUInt32 m_nNumberOfFrames; + XnUInt64 m_nMinTimestamp; + XnUInt64 m_nMaxTimestamp; +}; + +class NodeAddedRecord : public NodeAdded_1_0_0_5_Record +{ +public: + NodeAddedRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + NodeAddedRecord(const Record& record); + + void SetSeekTablePosition(XnUInt64 nPos); + + XnUInt64 GetSeekTablePosition(); + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +private: + XnUInt64 m_nSeekTablePosition; +}; + +class NodeRemovedRecord : public Record +{ +public: + NodeRemovedRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + NodeRemovedRecord(const Record& record); + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); +}; + +class GeneralPropRecord : public Record +{ +public: + GeneralPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header, XnUInt32 nPropRecordType = RECORD_GENERAL_PROPERTY); + GeneralPropRecord(const Record& record); + + void SetPropName(const XnChar* strPropName); + void SetPropDataSize(XnUInt32 nPropDataSize); + void SetPropData(const void* pPropData); + + const XnChar* GetPropName() const; + XnUInt32 GetPropDataSize() const; + const void* GetPropData() const; + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); +private: + XnUInt32 m_nPropRecordType; + const XnChar* m_strPropName; + XnUInt32 m_nPropDataSize; + void* m_pPropData; +}; + +class IntPropRecord : public GeneralPropRecord +{ +public: + IntPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + IntPropRecord(const Record& record); + void SetValue(XnUInt64 nValue); + XnUInt64 GetValue() const; + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +private: + XnUInt64 m_nValue; +}; + +class RealPropRecord : public GeneralPropRecord +{ +public: + RealPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + RealPropRecord(const Record& record); + void SetValue(XnDouble dValue); + XnDouble GetValue() const; + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +private: + XnDouble m_dValue; +}; + +class StringPropRecord : public GeneralPropRecord +{ +public: + StringPropRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + StringPropRecord(const Record& record); + void SetValue(const XnChar* strValue); + const XnChar* GetValue() const; + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); +}; + +struct SeekInfo +{ + XnUInt32 m_nFrames; + XnUInt64 m_nMaxTimeStamp; +}; + +class NodeDataBeginRecord : public Record +{ +public: + NodeDataBeginRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + NodeDataBeginRecord(const Record& record); + + XnUInt32 GetNumFrames() const; + XnUInt64 GetMaxTimeStamp() const; + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +private: + SeekInfo m_seekInfo; +}; + +class NodeStateReadyRecord : public Record +{ +public: + NodeStateReadyRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + NodeStateReadyRecord(const Record& record); + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); +}; + +/*This class represents only the data record HEADER, not the data itself. + This is because we want to be able to read/write the data directly without copying it.*/ +class NewDataRecordHeader : public Record +{ +public: + enum {MAX_SIZE = Record::HEADER_SIZE_current + //Record header + (XN_MAX_NAME_LENGTH + 1) + //Max node name + terminating null + sizeof(XnUInt64) + //Data timestamp + sizeof(XnUInt32)}; //Frame number + + NewDataRecordHeader(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + NewDataRecordHeader(const Record& record); + void SetTimeStamp(XnUInt64 nTimeStamp); + void SetFrameNumber(XnUInt32 nFrameNumber); + + XnUInt64 GetTimeStamp() const; + XnUInt32 GetFrameNumber() const; + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); + +private: + XnUInt64 m_nTimeStamp; + XnUInt32 m_nFrameNumber; +}; + +typedef struct +{ + XnUInt64 nTimestamp; + XnUInt32 nConfigurationID; + XnUInt32 nSeekPos; +} DataIndexEntry_old32; + +typedef struct _DataIndexEntry +{ + XnUInt64 nTimestamp; + XnUInt32 nConfigurationID; + XnUInt64 nSeekPos; + + static void FillFromOld32Entry(struct _DataIndexEntry *newEntry, DataIndexEntry_old32 *old32Entry) + { + newEntry->nTimestamp = old32Entry->nTimestamp; + newEntry->nConfigurationID = old32Entry->nConfigurationID; + newEntry->nSeekPos = old32Entry->nSeekPos; + } +} DataIndexEntry; + +class DataIndexRecordHeader : public Record +{ +public: + DataIndexRecordHeader(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + DataIndexRecordHeader(const Record& record); + + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); +}; + +class EndRecord : public Record +{ +public: + EndRecord(XnUInt8* pData, XnUInt32 nMaxSize, XnBool bUseOld32Header); + EndRecord(const Record& record); + XnStatus Encode(); + XnStatus Decode(); + XnStatus AsString(XnChar* strDest, XnUInt32 nSize, XnUInt32& nCharsWritten); +}; +#pragma pack(pop) + +#endif //__DATA_RECORDS_H__ diff --git a/Source/Drivers/OniFile/Formats/Xn16zCodec.h b/Source/Drivers/OniFile/Formats/Xn16zCodec.h new file mode 100644 index 0000000..3b94581 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/Xn16zCodec.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_16Z_CODEC_H__ +#define __XN_16Z_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include "XnStreamCompression.h" +#include "XnCodecIDs.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class Xn16zCodec : public XnCodecBase +{ +public: + virtual XnCodecID GetCodecID() const { return XN_CODEC_16Z; } + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_16Z; } + + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_DEPTH16Z_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + return XnStreamCompressDepth16Z((XnUInt16*)pData, nDataSize, pCompressedData, pnCompressedDataSize); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressDepth16Z(pCompressedData, nCompressedDataSize, (XnUInt16*)pData, pnDataSize); + } +}; + +#endif //__XN_16Z_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/Formats/Xn16zEmbTablesCodec.h b/Source/Drivers/OniFile/Formats/Xn16zEmbTablesCodec.h new file mode 100644 index 0000000..a3c6366 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/Xn16zEmbTablesCodec.h @@ -0,0 +1,60 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_16Z_EMB_TABLES_CODEC_H__ +#define __XN_16Z_EMB_TABLES_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include "XnStreamCompression.h" +#include "XnCodecIDs.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class Xn16zEmbTablesCodec : public XnCodecBase +{ +public: + Xn16zEmbTablesCodec(XnUInt16 nMaxValue) : m_nMaxValue(nMaxValue) {} + + virtual XnCodecID GetCodecID() const { return XN_CODEC_16Z_EMB_TABLES; } + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_16Z_EMB_TABLE; } + + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_DEPTH16Z_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return m_nMaxValue * sizeof(XnUInt16); } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + return XnStreamCompressDepth16ZWithEmbTable((XnUInt16*)pData, nDataSize, pCompressedData, pnCompressedDataSize, m_nMaxValue); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressDepth16ZWithEmbTable(pCompressedData, nCompressedDataSize, (XnUInt16*)pData, pnDataSize); + } + +private: + XnUInt16 m_nMaxValue; +}; + +#endif //__XN_16Z_EMB_TABLES_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/Formats/Xn8zCodec.h b/Source/Drivers/OniFile/Formats/Xn8zCodec.h new file mode 100644 index 0000000..ef2e991 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/Xn8zCodec.h @@ -0,0 +1,54 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_8Z_CODEC_H__ +#define __XN_8Z_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include "XnStreamCompression.h" +#include "XnCodecIDs.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class Xn8zCodec : public XnCodecBase +{ +public: + virtual XnCodecID GetCodecID() const { return XN_CODEC_8Z; } + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_COLOR_8Z; } + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_IMAGE8Z_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + return XnStreamCompressImage8Z(pData, nDataSize, pCompressedData, pnCompressedDataSize); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressImage8Z(pCompressedData, nCompressedDataSize, pData, pnDataSize); + } +}; + +#endif //__XN_8Z_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/Formats/XnCodec.cpp b/Source/Drivers/OniFile/Formats/XnCodec.cpp new file mode 100644 index 0000000..40d5c55 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnCodec.cpp @@ -0,0 +1,63 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodec.h" +#include "XnCodecIDs.h" + +XnCompressionFormats XnCodec::GetCompressionFormatFromCodecID(XnCodecID codecID) +{ + switch (codecID) + { + case XN_CODEC_UNCOMPRESSED: + return XN_COMPRESSION_NONE; + case XN_CODEC_16Z: + return XN_COMPRESSION_16Z; + case XN_CODEC_16Z_EMB_TABLES: + return XN_COMPRESSION_16Z_EMB_TABLE; + case XN_CODEC_8Z: + return XN_COMPRESSION_COLOR_8Z; + case XN_CODEC_JPEG: + return XN_COMPRESSION_JPEG; + default: + return (XnCompressionFormats)-1; + } +} + +XnCodecID XnCodec::GetCodecIDFromCompressionFormat(XnCompressionFormats format) +{ + switch (format) + { + case XN_COMPRESSION_16Z: + return XN_CODEC_16Z; + case XN_COMPRESSION_16Z_EMB_TABLE: + return XN_CODEC_16Z_EMB_TABLES; + case XN_COMPRESSION_JPEG: + return XN_CODEC_JPEG; + case XN_COMPRESSION_NONE: + return XN_CODEC_UNCOMPRESSED; + case XN_COMPRESSION_COLOR_8Z: + return XN_CODEC_8Z; + default: + return XN_CODEC_NULL; + } +} diff --git a/Source/Drivers/OniFile/Formats/XnCodec.h b/Source/Drivers/OniFile/Formats/XnCodec.h new file mode 100644 index 0000000..3aa3fca --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnCodec.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_CODEC_H__ +#define __XN_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnStatus.h" +#include "XnStreamFormats.h" + +typedef XnUInt32 XnCodecID; + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnCodec +{ +public: + XnCodec() {} + virtual ~XnCodec() {} + + virtual XnCodecID GetCodecID() const = 0; + + virtual XnStatus Init() { return XN_STATUS_OK; } + + virtual XnCompressionFormats GetCompressionFormat() const = 0; + + virtual XnStatus Compress(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) = 0; + + virtual XnStatus Decompress(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) = 0; + + static XnCompressionFormats GetCompressionFormatFromCodecID(XnCodecID codecID); + static XnCodecID GetCodecIDFromCompressionFormat(XnCompressionFormats format); +}; + +#endif //__XN_CODEC_H__ diff --git a/Source/Drivers/OniFile/Formats/XnCodecBase.h b/Source/Drivers/OniFile/Formats/XnCodecBase.h new file mode 100644 index 0000000..71c6774 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnCodecBase.h @@ -0,0 +1,86 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_CODEC_BASE_H__ +#define __XN_CODEC_BASE_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodec.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnCodecBase : public XnCodec +{ +public: + static XnCompressionFormats GetCompressionFormatFromCodecID(XnCodecID codecID); + static XnCodecID GetCodecIDFromCompressionFormat(XnCompressionFormats format); + + XnCodecBase() {} + virtual ~XnCodecBase() {} + + virtual XnStatus Init() { return XN_STATUS_OK; } + + virtual XnCompressionFormats GetCompressionFormat() const = 0; + + XnStatus Compress(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pData); + XN_VALIDATE_INPUT_PTR(pCompressedData); + XN_VALIDATE_OUTPUT_PTR(pnCompressedDataSize); + + if ((nDataSize * GetWorseCompressionRatio() + GetOverheadSize()) > *pnCompressedDataSize) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + nRetVal = CompressImpl(pData, nDataSize, pCompressedData, pnCompressedDataSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); + } + + XnStatus Decompress(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pCompressedData); + XN_VALIDATE_INPUT_PTR(pData); + XN_VALIDATE_OUTPUT_PTR(pnDataSize); + + nRetVal = DecompressImpl(pCompressedData, nCompressedDataSize, pData, pnDataSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); + } + + virtual XnUInt32 GetOverheadSize() const = 0; + virtual XnFloat GetWorseCompressionRatio() const = 0; + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) = 0; + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) = 0; +}; + +#endif // __XN_CODEC_BASE_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/Formats/XnCodecIDs.h b/Source/Drivers/OniFile/Formats/XnCodecIDs.h new file mode 100644 index 0000000..e8ecc6d --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnCodecIDs.h @@ -0,0 +1,34 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __NICODECIDS_H__ +#define __NICODECIDS_H__ + +/** Define a Codec ID by 4 characters, e.g. XN_CODEC_ID('J','P','E','G') **/ +#define XN_CODEC_ID(c1, c2, c3, c4) (XnCodecID)((c4 << 24) | (c3 << 16) | (c2 << 8) | c1) + +#define XN_CODEC_NULL XN_CODEC_ID(0, 0, 0, 0) +#define XN_CODEC_UNCOMPRESSED XN_CODEC_ID('N','O','N','E') +#define XN_CODEC_JPEG XN_CODEC_ID('J','P','E','G') +#define XN_CODEC_16Z XN_CODEC_ID('1','6','z','P') +#define XN_CODEC_16Z_EMB_TABLES XN_CODEC_ID('1','6','z','T') +#define XN_CODEC_8Z XN_CODEC_ID('I','m','8','z') + +#endif // __NICODECIDS_H__ diff --git a/Source/Drivers/OniFile/Formats/XnJpegCodec.h b/Source/Drivers/OniFile/Formats/XnJpegCodec.h new file mode 100644 index 0000000..8ae3689 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnJpegCodec.h @@ -0,0 +1,99 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_JPEG_CODEC_H__ +#define __XN_JPEG_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include "XnStreamCompression.h" +#include "XnCodecIDs.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnJpegCodec : public XnCodecBase +{ +public: + XnJpegCodec(XnBool bRGB, XnUInt32 nXRes, XnUInt32 nYRes, XnUInt32 nQuality = XN_STREAM_COMPRESSION_JPEG_DEFAULT_QUALITY) : + m_bRGB(bRGB), m_nXRes(nXRes), m_nYRes(nYRes), m_nQuality(nQuality) + {} + + ~XnJpegCodec() + { + XnStreamFreeCompressImageJ(&m_CompJPEGContext); + XnStreamFreeUncompressImageJ(&m_UncompJPEGContext); + } + + virtual XnCodecID GetCodecID() const { return XN_CODEC_JPEG; } + + XnStatus Init() + { + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnStreamInitCompressImageJ(&m_CompJPEGContext); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnStreamInitUncompressImageJ(&m_UncompJPEGContext); + if (nRetVal != XN_STATUS_OK) + { + XnStreamFreeCompressImageJ(&m_CompJPEGContext); + return (nRetVal); + } + + return (XN_STATUS_OK); + } + + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_JPEG; } + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_IMAGEJ_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + XN_DISABLE_COPY_AND_ASSIGN(XnJpegCodec); + + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 /*nDataSize*/, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + if (m_bRGB) + { + return XnStreamCompressImage24J(&m_CompJPEGContext, pData, pCompressedData, pnCompressedDataSize, m_nXRes, m_nYRes, m_nQuality); + } + else + { + return XnStreamCompressImage8J(&m_CompJPEGContext, pData, pCompressedData, pnCompressedDataSize, m_nXRes, m_nYRes, m_nQuality); + } + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressImageJ(&m_UncompJPEGContext, pCompressedData, nCompressedDataSize, pData, pnDataSize); + } + +private: + const XnBool m_bRGB; + const XnUInt32 m_nXRes; + const XnUInt32 m_nYRes; + const XnUInt32 m_nQuality; + XnStreamCompJPEGContext m_CompJPEGContext; + XnStreamUncompJPEGContext m_UncompJPEGContext; +}; + +#endif //__XN_JPEG_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/Formats/XnStreamCompression.cpp b/Source/Drivers/OniFile/Formats/XnStreamCompression.cpp new file mode 100644 index 0000000..ddd0d5b --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnStreamCompression.cpp @@ -0,0 +1,1314 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnStreamCompression.h" +#include +#include +#include + +#define XN_MASK_STREAM_COMPRESSION "xnStreamCompression" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnStreamCompressDepth16Z(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt16* pInputEnd = pInput + (nInputSize / sizeof(XnUInt16)); + XnUInt8* pOrigOutput = pOutput; + XnUInt16 nCurrValue = 0; + XnUInt16 nLastValue = 0; + XnUInt16 nAbsDiffValue = 0; + XnInt16 nDiffValue = 0; + XnUInt8 cOutStage = 0; + XnUInt8 cOutChar = 0; + XnUInt8 cZeroCounter = 0; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize == 0) + { + *pnOutputSize = 0; + return XN_STATUS_OK; + } + + // Encode the data... + nLastValue = *pInput; + *(XnUInt16*)pOutput = nLastValue; + pInput++; + pOutput+=2; + + while (pInput != pInputEnd) + { + nCurrValue = *pInput; + + nDiffValue = (nLastValue - nCurrValue); + nAbsDiffValue = (XnUInt16)abs(nDiffValue); + + if (nAbsDiffValue <= 6) + { + nDiffValue += 6; + + if (cOutStage == 0) + { + cOutChar = (XnUInt8)(nDiffValue << 4); + + cOutStage = 1; + } + else + { + cOutChar += (XnUInt8)nDiffValue; + + if (cOutChar == 0x66) + { + cZeroCounter++; + + if (cZeroCounter == 15) + { + *pOutput = 0xEF; + pOutput++; + + cZeroCounter = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + *pOutput = cOutChar; + pOutput++; + } + + cOutStage = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + if (cOutStage == 0) + { + cOutChar = 0xFF; + } + else + { + cOutChar += 0x0F; + cOutStage = 0; + } + + *pOutput = cOutChar; + pOutput++; + + if (nAbsDiffValue <= 63) + { + nDiffValue += 192; + + *pOutput = (XnUInt8)nDiffValue; + pOutput++; + } + else + { + *(XnUInt16*)pOutput = (nCurrValue << 8) + (nCurrValue >> 8); + pOutput+=2; + } + } + + nLastValue = nCurrValue; + pInput++; + } + + if (cOutStage != 0) + { + *pOutput = cOutChar + 0x0D; + pOutput++; + } + + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressDepth16ZWithEmbTable(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nMaxValue) +{ + // Local function variables + const XnUInt16* pInputEnd = pInput + (nInputSize / sizeof(XnUInt16)); + const XnUInt16* pOrigInput = pInput; + const XnUInt8* pOrigOutput = pOutput; + XnUInt16 nCurrValue = 0; + XnUInt16 nLastValue = 0; + XnUInt16 nAbsDiffValue = 0; + XnInt16 nDiffValue = 0; + XnUInt8 cOutStage = 0; + XnUInt8 cOutChar = 0; + XnUInt8 cZeroCounter = 0; + static XnUInt16 nEmbTable[XN_MAX_UINT16]; + XnUInt16 nEmbTableIdx=0; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + // Create the embedded value translation table... + pOutput+=2; + xnOSMemSet(&nEmbTable[0], 0, nMaxValue*sizeof(XnUInt16)); + + while (pInput != pInputEnd) + { + nEmbTable[*pInput] = 1; + pInput++; + } + + for (XnUInt32 i=0; i> 8)); + pOutput+=2; + } + } + + nLastValue = nCurrValue; + pInput++; + } + + if (cOutStage != 0) + { + *pOutput = cOutChar + 0x0D; + pOutput++; + } + + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressDepth16Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + XnUInt16* pOutputEnd = 0; + const XnUInt16* pOrigOutput = pOutput; + XnUInt16 nLastFullValue = 0; + XnUInt8 cInput = 0; + XnUInt8 cZeroCounter = 0; + XnInt8 cInData1 = 0; + XnInt8 cInData2 = 0; + XnUInt8 cInData3 = 0; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt16)) + { + xnLogError(XN_MASK_STREAM_COMPRESSION, "Input size too small"); + return (XN_STATUS_BAD_PARAM); + } + + pOutputEnd = pOutput + (*pnOutputSize / sizeof(XnUInt16)); + + // Decode the data... + nLastFullValue = *(XnUInt16*)pInput; + *pOutput = nLastFullValue; + pInput+=2; + pOutput++; + + while (pInput != pInputEnd) + { + cInput = *pInput; + + if (cInput < 0xE0) + { + cInData1 = cInput >> 4; + cInData2 = (cInput & 0x0f); + + nLastFullValue -= (cInData1 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + pOutput++; + + if (cInData2 != 0x0f) + { + if (cInData2 != 0x0d) + { + nLastFullValue -= (cInData2 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + pOutput++; + } + + pInput++; + } + else + { + pInput++; + + cInData3 = *pInput; + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pOutput++; + pInput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pOutput++; + pInput++; + } + } + } + else if (cInput == 0xFF) + { + pInput++; + + cInData3 = *pInput; + + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pInput++; + pOutput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pInput++; + pOutput++; + } + } + else //It must be 0xE? + { + cZeroCounter = cInput - 0xE0; + + while (cZeroCounter != 0) + { + XN_CHECK_OUTPUT_OVERFLOW(pOutput+1, pOutputEnd); + *pOutput = nLastFullValue; + pOutput++; + + *pOutput = nLastFullValue; + pOutput++; + + cZeroCounter--; + } + + pInput++; + } + } + + *pnOutputSize = (XnUInt32)((pOutput - pOrigOutput) * sizeof(XnUInt16)); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressDepth16ZWithEmbTable(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + XnUInt16* pOutputEnd = 0; + XnUInt16* pOrigOutput = pOutput; + XnUInt16 nLastFullValue = 0; + XnUInt8 cInput = 0; + XnUInt8 cZeroCounter = 0; + XnInt8 cInData1 = 0; + XnInt8 cInData2 = 0; + XnUInt8 cInData3 = 0; + XnUInt16* pEmbTable = NULL; + XnUInt16 nEmbTableIdx = 0; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt16)) + { + xnLogError(XN_MASK_STREAM_COMPRESSION, "Input size too small"); + return (XN_STATUS_BAD_PARAM); + } + + nEmbTableIdx = XN_PREPARE_VAR16_IN_BUFFER(*(XnUInt16*)pInput); + pInput+=2; + pEmbTable = (XnUInt16*)pInput; + pInput+=nEmbTableIdx * 2; + for (XnUInt32 i = 0; i < nEmbTableIdx; i++) + pEmbTable[i] = XN_PREPARE_VAR16_IN_BUFFER(pEmbTable[i]); + + pOutputEnd = pOutput + (*pnOutputSize / sizeof(XnUInt16)); + + // Decode the data... + nLastFullValue = XN_PREPARE_VAR16_IN_BUFFER(*(XnUInt16*)pInput); + *pOutput = pEmbTable[nLastFullValue]; + pInput+=2; + pOutput++; + + while (pInput != pInputEnd) + { + cInput = *pInput; + + if (cInput < 0xE0) + { + cInData1 = cInput >> 4; + cInData2 = (cInput & 0x0f); + + nLastFullValue -= (cInData1 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + + if (cInData2 != 0x0f) + { + if (cInData2 != 0x0d) + { + nLastFullValue -= (cInData2 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + } + + pInput++; + } + else + { + pInput++; + + cInData3 = *pInput; + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pOutput++; + pInput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pOutput++; + pInput++; + } + } + } + else if (cInput == 0xFF) + { + pInput++; + + cInData3 = *pInput; + + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pInput++; + pOutput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pInput++; + pOutput++; + } + } + else //It must be 0xE? + { + cZeroCounter = cInput - 0xE0; + + while (cZeroCounter != 0) + { + XN_CHECK_OUTPUT_OVERFLOW(pOutput+1, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + + cZeroCounter--; + } + + pInput++; + } + } + + *pnOutputSize = (XnUInt32)((pOutput - pOrigOutput) * sizeof(XnUInt16)); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOrigOutput = pOutput; + XnUInt8 nCurrValue = 0; + XnUInt8 nLastValue = 0; + XnUInt8 nAbsDiffValue = 0; + XnInt8 nDiffValue = 0; + XnUInt8 cOutStage = 0; + XnUInt8 cOutChar = 0; + XnUInt8 cZeroCounter = 0; + XnBool bFlag = FALSE; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + // Encode the data... + nLastValue = *pInput; + *pOutput = nLastValue; + pInput++; + pOutput++; + + while (pInput != pInputEnd) + { + nCurrValue = *pInput; + + nDiffValue = (nLastValue - nCurrValue); + nAbsDiffValue = (XnUInt8)abs(nDiffValue); + + if (nAbsDiffValue <= 6) + { + nDiffValue += 6; + + if (cOutStage == 0) + { + cOutChar = nDiffValue << 4; + + cOutStage = 1; + } + else + { + cOutChar += nDiffValue; + + if ((cOutChar == 0x66) && (bFlag == FALSE)) + { + cZeroCounter++; + + if (cZeroCounter == 15) + { + *pOutput = 0xEF; + pOutput++; + + cZeroCounter = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + *pOutput = cOutChar; + pOutput++; + + bFlag = FALSE; + } + + cOutStage = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + if (cOutStage == 0) + { + cOutChar = 0xF0; + cOutChar += nCurrValue >> 4; + + *pOutput = cOutChar; + pOutput++; + + cOutChar = (nCurrValue & 0xF) << 4; + cOutStage = 1; + + bFlag = TRUE; + } + else + { + cOutChar += 0x0F; + cOutStage = 0; + + *pOutput = cOutChar; + pOutput++; + + *pOutput = nCurrValue; + pOutput++; + } + } + + nLastValue = nCurrValue; + pInput++; + } + + if (cOutStage != 0) + { + *pOutput = cOutChar + 0x0D; + pOutput++; + } + + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOrigOutput = pOutput; + XnUInt8 nLastFullValue = 0; + XnUInt8 cInput = 0; + XnUInt8 cZeroCounter = 0; + XnInt8 cInData1 = 0; + XnInt8 cInData2 = 0; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt8)) + { + xnLogError(XN_MASK_STREAM_COMPRESSION, "Input size too small"); + return (XN_STATUS_BAD_PARAM); + } + + // Decode the data... + nLastFullValue = *pInput; + *pOutput = nLastFullValue; + pInput++; + pOutput++; + + while (pInput != pInputEnd) + { + cInput = *pInput; + + if (cInput < 0xE0) + { + cInData1 = cInput >> 4; + cInData2 = (cInput & 0x0f); + + nLastFullValue -= (cInData1 - 6); + *pOutput = nLastFullValue; + pOutput++; + + if (cInData2 != 0x0f) + { + if (cInData2 != 0x0d) + { + nLastFullValue -= (cInData2 - 6); + *pOutput = nLastFullValue; + pOutput++; + } + } + else + { + pInput++; + nLastFullValue = *pInput; + *pOutput = nLastFullValue; + pOutput++; + } + + pInput++; + } + else if (cInput >= 0xF0) + { + cInData1 = cInput << 4; + + pInput++; + cInput = *pInput; + + nLastFullValue = cInData1 + (cInput >> 4); + + *pOutput = nLastFullValue; + pOutput++; + + cInData2 = cInput & 0xF; + + if (cInData2 == 0x0F) + { + pInput++; + nLastFullValue = *pInput; + *pOutput = nLastFullValue; + pOutput++; + pInput++; + } + else + { + if (cInData2 != 0x0D) + { + nLastFullValue -= (cInData2 - 6); + *pOutput = nLastFullValue; + pOutput++; + } + + pInput++; + } + } + else //It must be 0xE? + { + cZeroCounter = cInput - 0xE0; + + while (cZeroCounter != 0) + { + *pOutput = nLastFullValue; + pOutput++; + + *pOutput = nLastFullValue; + pOutput++; + + cZeroCounter--; + } + + pInput++; + } + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOrigOutput = pOutput; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + // Encode the data... + while (pInput != pInputEnd) + { + *pOutput = *pInput << 4; + pInput++; + + *pOutput += *pInput; + pInput++; + + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOutputEnd = 0; + const XnUInt8* pOrigOutput = pOutput; + XnUInt8 nValue1; + XnUInt8 nValue2; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt8)) + { + xnLogError(XN_MASK_STREAM_COMPRESSION, "Input size too small"); + return (XN_STATUS_BAD_PARAM); + } + + if (nInputSize % 2 != 0) + { + xnLogError(XN_MASK_STREAM_COMPRESSION, "Input size not word-aligned"); + return (XN_STATUS_BAD_PARAM); + } + + pOutputEnd = pOutput + *pnOutputSize; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput + (nInputSize * 2), pOutputEnd); + + while (pInput != pInputEnd) + { + nValue1 = pInput[0]; + nValue2 = pInput[1]; + + pOutput[0] = nValue1 >> 4; + pOutput[1] = nValue1 & 0xF; + pOutput[2] = nValue2 >> 4; + pOutput[3] = nValue2 & 0xF; + + pOutput+=4; + pInput+=2; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +void XnStreamJPEGCompDummyFunction(struct jpeg_compress_struct* /*pjCompStruct*/) +{ + // Dummy libjpeg function to wrap internal buffers usage... +} + +boolean XnStreamJPEGCompDummyFailFunction(struct jpeg_compress_struct* /*pjCompStruct*/) +{ + // If we ever got to the point we need to allocate more memory, something is wrong! + return (FALSE); +} + +void XnStreamJPEGDummyErrorExit(j_common_ptr cinfo) +{ + XnLibJpegErrorMgr* errMgr = (XnLibJpegErrorMgr*)cinfo->err; + + longjmp(errMgr->setjmpBuffer, 1); +} + +void XnStreamJPEGOutputMessage(j_common_ptr cinfo) +{ + struct jpeg_error_mgr* err = cinfo->err; + int msg_code = err->msg_code; + if (msg_code == JWRN_EXTRANEOUS_DATA) + { + // NOTE: we are aware this problem occurs. Log a warning every once in a while + static XnUInt32 nTimes = 0; + if (++nTimes == 50) + { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + //Temporary disabled this error since it happens all the time and it's a known issue. + //xnLogWarning(XN_MASK_JPEG, "JPEG: The following warning occurred 50 times: %s", buffer); + nTimes = 0; + } + } + else + { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + xnLogWarning(XN_MASK_JPEG, "JPEG: %s", buffer); + } +} + +XnStatus XnStreamInitCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_OUTPUT_PTR(pStreamCompJPEGContext); + + pStreamCompJPEGContext->jCompStruct.err = jpeg_std_error(&pStreamCompJPEGContext->jErrMgr.pub); + pStreamCompJPEGContext->jErrMgr.pub.output_message = XnStreamJPEGOutputMessage; + pStreamCompJPEGContext->jErrMgr.pub.error_exit = XnStreamJPEGDummyErrorExit; + + jpeg_create_compress(&pStreamCompJPEGContext->jCompStruct); + + pStreamCompJPEGContext->jCompStruct.dest = &pStreamCompJPEGContext->jDestMgr; + pStreamCompJPEGContext->jCompStruct.dest->init_destination = XnStreamJPEGCompDummyFunction; + pStreamCompJPEGContext->jCompStruct.dest->empty_output_buffer = XnStreamJPEGCompDummyFailFunction; + pStreamCompJPEGContext->jCompStruct.dest->term_destination = XnStreamJPEGCompDummyFunction; + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamFreeCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamCompJPEGContext); + + jpeg_destroy_compress(&pStreamCompJPEGContext->jCompStruct); + + // All is good... + return (XN_STATUS_OK); +} + +#ifdef _WIN32 +// to allow the use of setjmp +#pragma warning(push) +#pragma warning(disable: 4611) +#endif + +XnStatus XnStreamCompressImage8J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality) +{ + // Local function variables + XnUInt8* pCurrScanline = (XnUInt8*)pInput; + XnUInt32 nYIndex = 0; + jpeg_compress_struct* pjCompStruct = NULL; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamCompJPEGContext); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_OUTPUT_PTR(pOutput); + XN_VALIDATE_OUTPUT_PTR(pnOutputSize); + + pjCompStruct = &pStreamCompJPEGContext->jCompStruct; + + if (setjmp(pStreamCompJPEGContext->jErrMgr.setjmpBuffer)) + { + //If we get here, the JPEG code has signaled an error. + XnStreamFreeCompressImageJ(pStreamCompJPEGContext); + XnStreamInitCompressImageJ(pStreamCompJPEGContext); + + *pnOutputSize = 0; + + xnLogError(XN_MASK_JPEG, "JPEG compressor error :("); + return (XN_STATUS_ERROR); + } + + pjCompStruct->in_color_space = JCS_GRAYSCALE; + jpeg_set_defaults(pjCompStruct); + pjCompStruct->input_components = 1; + pjCompStruct->num_components = 1; + pjCompStruct->image_width = nXRes; + pjCompStruct->image_height = nYRes; + pjCompStruct->data_precision = 8; + pjCompStruct->input_gamma = 1.0; + + jpeg_set_quality(pjCompStruct, nQuality, FALSE); + + pjCompStruct->dest->next_output_byte = (JOCTET*)pOutput; + pjCompStruct->dest->free_in_buffer = *pnOutputSize; + + jpeg_start_compress(pjCompStruct, TRUE); + + for (nYIndex = 0; nYIndex < nYRes; nYIndex++) + { + jpeg_write_scanlines(pjCompStruct, &pCurrScanline, 1); + + pCurrScanline += nXRes; + } + + jpeg_finish_compress(pjCompStruct); + + *pnOutputSize -= (XnUInt32)pjCompStruct->dest->free_in_buffer; + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressImage24J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality) +{ + // Local function variables + XnUInt8* pCurrScanline = (XnUChar*)pInput; + XnUInt32 nYIndex = 0; + XnUInt32 nScanLineSize = 0; + jpeg_compress_struct* pjCompStruct = NULL; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamCompJPEGContext); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_OUTPUT_PTR(pOutput); + XN_VALIDATE_OUTPUT_PTR(pnOutputSize); + + pjCompStruct = &pStreamCompJPEGContext->jCompStruct; + + if (setjmp(pStreamCompJPEGContext->jErrMgr.setjmpBuffer)) + { + //If we get here, the JPEG code has signaled an error. + XnStreamFreeCompressImageJ(pStreamCompJPEGContext); + XnStreamInitCompressImageJ(pStreamCompJPEGContext); + + *pnOutputSize = 0; + + xnLogError(XN_MASK_JPEG, "JPEG compressor error :("); + return (XN_STATUS_ERROR); + } + + pjCompStruct->in_color_space = JCS_RGB; + jpeg_set_defaults(pjCompStruct); + pjCompStruct->input_components = 3; + pjCompStruct->num_components = 3; + pjCompStruct->image_width = nXRes; + pjCompStruct->image_height = nYRes; + pjCompStruct->data_precision = 8; + pjCompStruct->input_gamma = 1.0; + + jpeg_set_quality(pjCompStruct, nQuality, FALSE); + + pjCompStruct->dest->next_output_byte = (JOCTET*)pOutput; + pjCompStruct->dest->free_in_buffer = *pnOutputSize; + + jpeg_start_compress(pjCompStruct, TRUE); + + nScanLineSize = nXRes * 3; + for (nYIndex = 0; nYIndex < nYRes; nYIndex++) + { + jpeg_write_scanlines(pjCompStruct, &pCurrScanline, 1); + + pCurrScanline += nScanLineSize; + } + + jpeg_finish_compress(pjCompStruct); + + *pnOutputSize -= (XnUInt32)pjCompStruct->dest->free_in_buffer; + + // All is good... + return (XN_STATUS_OK); +} + +void XnStreamJPEGDecompDummyFunction(struct jpeg_decompress_struct* /*pjDecompStruct*/) +{ + // Dummy libjpeg function to wrap internal buffers usage... +} + +boolean XnStreamJPEGDecompDummyFailFunction(struct jpeg_decompress_struct* /*pjDecompStruct*/) +{ + // If we ever got to the point we need to allocate more memory, something is wrong! + return (FALSE); +} + +void XnStreamJPEGDecompSkipFunction(struct jpeg_decompress_struct* pjDecompStruct, long nNumBytes) +{ + // Skip bytes in the internal buffer + pjDecompStruct->src->next_input_byte += (size_t)nNumBytes; + pjDecompStruct->src->bytes_in_buffer -= (size_t)nNumBytes; +} + +XnStatus XnStreamInitUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_OUTPUT_PTR(pStreamUncompJPEGContext); + + pStreamUncompJPEGContext->jDecompStruct.err = jpeg_std_error(&pStreamUncompJPEGContext->jErrMgr.pub); + pStreamUncompJPEGContext->jErrMgr.pub.output_message = XnStreamJPEGOutputMessage; + pStreamUncompJPEGContext->jErrMgr.pub.error_exit = XnStreamJPEGDummyErrorExit; + + jpeg_create_decompress(&pStreamUncompJPEGContext->jDecompStruct); + + pStreamUncompJPEGContext->jDecompStruct.src = &pStreamUncompJPEGContext->jSrcMgr; + pStreamUncompJPEGContext->jDecompStruct.src->init_source = XnStreamJPEGDecompDummyFunction; + pStreamUncompJPEGContext->jDecompStruct.src->fill_input_buffer = XnStreamJPEGDecompDummyFailFunction; + pStreamUncompJPEGContext->jDecompStruct.src->skip_input_data = XnStreamJPEGDecompSkipFunction; + pStreamUncompJPEGContext->jDecompStruct.src->resync_to_restart = jpeg_resync_to_restart; + pStreamUncompJPEGContext->jDecompStruct.src->term_source = XnStreamJPEGDecompDummyFunction; + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamFreeUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamUncompJPEGContext); + + jpeg_destroy_decompress(&pStreamUncompJPEGContext->jDecompStruct); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext, const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + XnUInt8* pCurrScanline = pOutput; + XnUInt8* pNextScanline = NULL; + XnUInt8* pOutputEnd = 0; + XnUInt32 nScanLineSize = 0; + XnUInt32 nOutputSize = 0; + jpeg_decompress_struct* pjDecompStruct = NULL; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamUncompJPEGContext); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_OUTPUT_PTR(pOutput); + XN_VALIDATE_OUTPUT_PTR(pnOutputSize); + + if (nInputSize == 0) + { + xnLogError(XN_MASK_JPEG, "Input size is 0"); + return (XN_STATUS_BAD_PARAM); + } + + pOutputEnd = pOutput + *pnOutputSize; + + pjDecompStruct = &pStreamUncompJPEGContext->jDecompStruct; + + pjDecompStruct->src->bytes_in_buffer = nInputSize; + pjDecompStruct->src->next_input_byte = pInput; + + if (setjmp(pStreamUncompJPEGContext->jErrMgr.setjmpBuffer)) + { + //If we get here, the JPEG code has signaled an error. + XnStreamFreeUncompressImageJ(pStreamUncompJPEGContext); + XnStreamInitUncompressImageJ(pStreamUncompJPEGContext); + + *pnOutputSize = 0; + + xnLogError(XN_MASK_JPEG, "JPEG compressor error :("); + return (XN_STATUS_ERROR); + } + + jpeg_read_header(pjDecompStruct, TRUE); + + jpeg_start_decompress(pjDecompStruct); + + nScanLineSize = pjDecompStruct->output_width * pjDecompStruct->num_components; + + nOutputSize = pjDecompStruct->output_height * nScanLineSize; + if (nOutputSize > *pnOutputSize) + { + XnStreamFreeUncompressImageJ(pStreamUncompJPEGContext); + XnStreamInitUncompressImageJ(pStreamUncompJPEGContext); + + *pnOutputSize = 0; + + xnLogError(XN_MASK_JPEG, "JPEG compressor error :("); + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + while (pStreamUncompJPEGContext->jDecompStruct.output_scanline < pStreamUncompJPEGContext->jDecompStruct.output_height) + { + pNextScanline = pCurrScanline+nScanLineSize; + + if (pNextScanline > pOutputEnd) + { + XnStreamFreeUncompressImageJ(pStreamUncompJPEGContext); + XnStreamInitUncompressImageJ(pStreamUncompJPEGContext); + + *pnOutputSize = 0; + + xnLogError(XN_MASK_JPEG, "JPEG compressor error :("); + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + jpeg_read_scanlines(pjDecompStruct, &pCurrScanline, 1); + pCurrScanline = pNextScanline; + } + + jpeg_finish_decompress(pjDecompStruct); + + *pnOutputSize = nOutputSize; + + // All is good... + return (XN_STATUS_OK); +} +#ifdef _WIN32 +#pragma warning(pop) +#endif diff --git a/Source/Drivers/OniFile/Formats/XnStreamCompression.h b/Source/Drivers/OniFile/Formats/XnStreamCompression.h new file mode 100644 index 0000000..79e84d1 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnStreamCompression.h @@ -0,0 +1,103 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_STREAMCOMPRESSION_H_ +#define _XN_STREAMCOMPRESSION_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +extern "C" { +#include +} +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_STREAM_COMPRESSION_DEPTH16Z_WORSE_RATIO 1.333F +#define XN_STREAM_COMPRESSION_IMAGE8Z_WORSE_RATIO 1.333F +#define XN_STREAM_COMPRESSION_IMAGEJ_WORSE_RATIO 1.2F +#define XN_STREAM_COMPRESSION_CONF4_WORSE_RATIO 0.51F +#define XN_STREAM_COMPRESSION_JPEG_DEFAULT_QUALITY 90 + +#define XN_STREAM_STRING_BAD_FORMAT -1 + +#define XN_MASK_JPEG "JPEG" + +//--------------------------------------------------------------------------- +// Structs +//--------------------------------------------------------------------------- + +XN_PRAGMA_START_DISABLED_WARNING_SECTION(XN_STRUCT_PADDED_WARNING_ID); + +typedef struct XnLibJpegErrorMgr +{ + struct jpeg_error_mgr pub; + + jmp_buf setjmpBuffer; +} XnLibJpegErrorMgr; + +XN_PRAGMA_STOP_DISABLED_WARNING_SECTION; + +typedef struct XnStreamCompJPEGContext +{ + jpeg_compress_struct jCompStruct; + XnLibJpegErrorMgr jErrMgr; + struct jpeg_destination_mgr jDestMgr; +} XnStreamCompJPEGContext; + +typedef struct XnStreamUncompJPEGContext +{ + jpeg_decompress_struct jDecompStruct; + XnLibJpegErrorMgr jErrMgr; + struct jpeg_source_mgr jSrcMgr; +} XnStreamUncompJPEGContext; + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +XnStatus XnStreamCompressDepth16Z(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamCompressDepth16ZWithEmbTable(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nMaxValue); +XnStatus XnStreamUncompressDepth16Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamUncompressDepth16ZWithEmbTable(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize); + +XnStatus XnStreamCompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamUncompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); + +XnStatus XnStreamCompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamUncompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); + +void XnStreamJPEGCompDummyFunction(struct jpeg_compress_struct* pjCompStruct); +boolean XnStreamJPEGCompDummyFailFunction(struct jpeg_compress_struct* pjCompStruct); +XnStatus XnStreamInitCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext); +XnStatus XnStreamFreeCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext); +XnStatus XnStreamCompressImage8J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality); +XnStatus XnStreamCompressImage24J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality); + +void XnStreamJPEGDecompDummyFunction(struct jpeg_decompress_struct* pjDecompStruct); +boolean XnStreamJPEGDecompDummyFailFunction(struct jpeg_decompress_struct* pjDecompStruct); +void XnStreamJPEGDecompSkipFunction(struct jpeg_decompress_struct* pjDecompStruct, XnInt nNumBytes); +XnStatus XnStreamInitUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext); +XnStatus XnStreamFreeUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext); +XnStatus XnStreamUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext, const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); + +#endif //_XN_STREAMCOMPRESSION_H_ diff --git a/Source/Drivers/OniFile/Formats/XnStreamFormats.h b/Source/Drivers/OniFile/Formats/XnStreamFormats.h new file mode 100644 index 0000000..818cfc3 --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnStreamFormats.h @@ -0,0 +1,49 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_STREAM_FORMATS_H__ +#define __XN_STREAM_FORMATS_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Formats +//--------------------------------------------------------------------------- + +typedef enum +{ + /** Data is stored uncompressed. */ + XN_COMPRESSION_NONE = 0, + /** Data is compressed using PS lossless 16-bit depth compression. */ + XN_COMPRESSION_16Z = 1, + /** Data is compressed using PS lossless 16-bit depth compression with embedded tables. */ + XN_COMPRESSION_16Z_EMB_TABLE = 2, + /** Data is compressed using PS lossless 8-bit image compression (for grayscale). */ + XN_COMPRESSION_COLOR_8Z = 3, + /** Data is compressed using JPEG. */ + XN_COMPRESSION_JPEG = 4, + /** Data is packed in 10-bit values. */ + XN_COMPRESSION_10BIT_PACKED = 5, +} XnCompressionFormats; + +#endif //__XN_STREAM_FORMATS_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/Formats/XnUncompressedCodec.h b/Source/Drivers/OniFile/Formats/XnUncompressedCodec.h new file mode 100644 index 0000000..ca9351a --- /dev/null +++ b/Source/Drivers/OniFile/Formats/XnUncompressedCodec.h @@ -0,0 +1,71 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_UNCOMPRESSED_CODEC_H__ +#define __XN_UNCOMPRESSED_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include "XnCodecIDs.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnUncompressedCodec : public XnCodecBase +{ +public: + XnUncompressedCodec() {} + virtual ~XnUncompressedCodec() {} + + virtual XnCodecID GetCodecID() const { return XN_CODEC_UNCOMPRESSED; } + + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_NONE; } + virtual XnFloat GetWorseCompressionRatio() const { return 1.0; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + if (nDataSize > *pnCompressedDataSize) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + xnOSMemCopy(pCompressedData, pData, nDataSize); + *pnCompressedDataSize = nDataSize; + return (XN_STATUS_OK); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + if (nCompressedDataSize > *pnDataSize) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + xnOSMemCopy(pData, pCompressedData, nCompressedDataSize); + *pnDataSize = nCompressedDataSize; + return (XN_STATUS_OK); + } +}; + +#endif //__XN_UNCOMPRESSED_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/Makefile b/Source/Drivers/OniFile/Makefile new file mode 100644 index 0000000..69daf8f --- /dev/null +++ b/Source/Drivers/OniFile/Makefile @@ -0,0 +1,37 @@ +include ../../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../../Bin + +INC_DIRS = \ + . \ + ../../../Include \ + ../../../ThirdParty/PSCommon/XnLib/Include \ + ../../../ThirdParty/LibJPEG \ + Formats + +SRC_FILES = \ + *.cpp \ + Formats/*.cpp \ + XnLibExtensions/*.cpp \ + ../../../ThirdParty/LibJPEG/*.c + + +ifeq ("$(OSTYPE)","Darwin") + INC_DIRS += /opt/local/include + LIB_DIRS += /opt/local/lib +# LDFLAGS += -framework CoreFoundation -framework IOKit +endif + +LIB_NAME = OniFile + +LIB_DIRS = ../../../ThirdParty/PSCommon/XnLib/Bin/$(PLATFORM)-$(CFG) +USED_LIBS = XnLib pthread +ifneq ("$(OSTYPE)","Darwin") + USED_LIBS += rt +endif + +CFLAGS += -Wall + +OUT_DIR := $(OUT_DIR)/OpenNI2/Drivers + +include ../../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Source/Drivers/OniFile/OniFile.vcxproj b/Source/Drivers/OniFile/OniFile.vcxproj new file mode 100644 index 0000000..9b173f7 --- /dev/null +++ b/Source/Drivers/OniFile/OniFile.vcxproj @@ -0,0 +1,752 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {15ECC029-90DE-4D1D-B00A-4A8E647D8C24} + Win32Proj + OniFile + + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + true + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + true + + + false + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + false + + + false + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + false + + + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ONIFILESPLAYER_EXPORTS;%(PreprocessorDefinitions) + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG + true + true + + + + + Windows + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + + + + + + + + + + + + + + + + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ONIFILESPLAYER_EXPORTS;%(PreprocessorDefinitions) + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG + true + true + + + + + Windows + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + + + + + + + + + + + + + + + + + Level4 + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ONIFILESPLAYER_EXPORTS;%(PreprocessorDefinitions) + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + Windows + true + true + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + + + + + + + + + + + + + + + + + Level4 + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ONIFILESPLAYER_EXPORTS;%(PreprocessorDefinitions) + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + Windows + true + true + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + Level3 + Level3 + false + false + Level3 + Level3 + false + false + + + + + + + + + + + + + + ..\..\..\Include + ..\..\..\Include + ..\..\..\Include + ..\..\..\Include + + + + + + \ No newline at end of file diff --git a/Source/Drivers/OniFile/OniFile.vcxproj.filters b/Source/Drivers/OniFile/OniFile.vcxproj.filters new file mode 100644 index 0000000..6b6a423 --- /dev/null +++ b/Source/Drivers/OniFile/OniFile.vcxproj.filters @@ -0,0 +1,257 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {966737e1-fa65-4fb1-a880-86f06afbf5b5} + + + {2b5f0f3a-dcdb-4faa-9b5f-d6b2d0c24222} + + + {8505174a-1146-4871-8927-07bbe186c5e5} + + + + + Header Files\Formats + + + Header Files + + + Header Files + + + Header Files\Formats + + + Header Files\Formats + + + Header Files\Formats + + + Header Files\Formats + + + Header Files\Formats + + + Header Files\Formats + + + Header Files\Formats + + + Header Files\Formats + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files\Formats + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files\Formats\LibJPEG + + + Source Files + + + Source Files\Formats + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Source/Drivers/OniFile/PlayerCodecFactory.cpp b/Source/Drivers/OniFile/PlayerCodecFactory.cpp new file mode 100644 index 0000000..dc61164 --- /dev/null +++ b/Source/Drivers/OniFile/PlayerCodecFactory.cpp @@ -0,0 +1,128 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "PlayerCodecFactory.h" +#include "Formats/XnUncompressedCodec.h" +#include "Formats/Xn16zCodec.h" +#include "Formats/Xn16zEmbTablesCodec.h" +#include "Formats/Xn8zCodec.h" +#include "Formats/XnJpegCodec.h" +#include "OniCProperties.h" +//#include + +namespace oni_file { + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus PlayerCodecFactory::Create(XnCodecID nCodecID, PlayerSource* pSource, XnCodec** ppCodec) +{ + OniStatus rc; + XnCodec* pCodec = NULL; + + switch (nCodecID) + { + case XN_CODEC_UNCOMPRESSED: + { + XN_VALIDATE_NEW_AND_INIT(pCodec, XnUncompressedCodec); + break; + } + case XN_CODEC_16Z: + { + XN_VALIDATE_NEW_AND_INIT(pCodec, Xn16zCodec); + break; + } + case XN_CODEC_16Z_EMB_TABLES: + { + // first we need to find max depth + int nMaxDepth; + int dataSize = sizeof(nMaxDepth); + rc = pSource->GetProperty(ONI_STREAM_PROPERTY_MAX_VALUE, &nMaxDepth, &dataSize); + if (rc != ONI_STATUS_OK) + { + return XN_STATUS_ERROR; + } + + XN_VALIDATE_NEW_AND_INIT(pCodec, Xn16zEmbTablesCodec, (OniDepthPixel)nMaxDepth); + break; + } + case XN_CODEC_8Z: + { + XN_VALIDATE_NEW_AND_INIT(pCodec, Xn8zCodec); + break; + } + case XN_CODEC_JPEG: + { + // check what is the output format + OniVideoMode videoMode; + int dataSize = sizeof(videoMode); + rc = pSource->GetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, &dataSize); + if (rc != ONI_STATUS_OK) + { + return XN_STATUS_ERROR; + } + + XnBool bRGB = FALSE; + + switch (videoMode.pixelFormat) + { + case ONI_PIXEL_FORMAT_GRAY8: + { + bRGB = FALSE; + break; + } + case ONI_PIXEL_FORMAT_RGB888: + { + bRGB = TRUE; + break; + } + default: + { + //XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Codec factory currently supports JPEG codec only for streams of type Gray8 or RGB24!"); + return XN_STATUS_ERROR; + } + } + + // take X and Y res + XnUInt32 nXRes = videoMode.resolutionX; + XnUInt32 nYRes = videoMode.resolutionY; + XN_VALIDATE_NEW_AND_INIT(pCodec, XnJpegCodec, bRGB, nXRes, nYRes); + break; + } + default: + { + //XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Codec factory does not support compression type %d", nFormat); + return XN_STATUS_ERROR; + } + } + + *ppCodec = pCodec; + return (XN_STATUS_OK); +} + +void PlayerCodecFactory::Destroy(XnCodec* pCodec) +{ + XN_DELETE(pCodec); +} + +} // namespace oni_files_player diff --git a/Source/Drivers/OniFile/PlayerCodecFactory.h b/Source/Drivers/OniFile/PlayerCodecFactory.h new file mode 100644 index 0000000..e054dbe --- /dev/null +++ b/Source/Drivers/OniFile/PlayerCodecFactory.h @@ -0,0 +1,44 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __PLAYER_CODEC_FACTORY_H__ +#define __PLAYER_CODEC_FACTORY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "Formats/XnCodec.h" +#include "PlayerSource.h" + +namespace oni_file { + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class PlayerCodecFactory +{ +public: + static XnStatus Create(XnCodecID nCodecID, PlayerSource* pSource, XnCodec** ppCodec); + static void Destroy(XnCodec* pCodec); +}; + +} // namespace oni_files_player + +#endif //__PLAYER_CODEC_FACTORY_H__ \ No newline at end of file diff --git a/Source/Drivers/OniFile/PlayerDevice.cpp b/Source/Drivers/OniFile/PlayerDevice.cpp new file mode 100644 index 0000000..c79efd8 --- /dev/null +++ b/Source/Drivers/OniFile/PlayerDevice.cpp @@ -0,0 +1,1147 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the definition of Device class that implements a virtual OpenNI +/// device, capable of reading data from a *.ONI file. + +#include "PlayerDevice.h" +#include "PlayerSource.h" +#include "PlayerStream.h" +#include "XnPropNames.h" +#include "OniProperties.h" +#include "XnMemory.h" +#include "Formats/XnCodec.h" +#include "PlayerCodecFactory.h" +#include "PS1080.h" + +namespace oni_file { + +namespace driver = oni::driver; + +#define DEVICE_DESTROY_THREAD_TIMEOUT 3000 +#define DEVICE_READY_FOR_DATA_EVENT_SANITY_SLEEP 2000 +#define DEVICE_MANUAL_TRIGGER_STANITY_SLEEP 2000 +#define XN_PLAYBACK_SPEED_SANITY_SLEEP 2000 +#define XN_PLAYBACK_SPEED_FASTEST 0.0 +#define XN_PLAYBACK_SPEED_MANUAL (-1.0) + +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) (sizeof(a)/sizeof((a)[0])) +#endif //ARRAYSIZE + +typedef struct +{ + // Identifier of the property. + XnUInt32 propertyId; + + // Name of the property. + XnChar propertyName[40]; + +} PS1080Property; + +static PS1080Property PS1080PropertyList[] = +{ + { XN_STREAM_PROPERTY_INPUT_FORMAT, "InputFormat" }, + { XN_STREAM_PROPERTY_CROPPING_MODE, "CroppingMode" }, + { XN_STREAM_PROPERTY_WHITE_BALANCE_ENABLED, "WhiteBalancedEnabled" }, + { XN_STREAM_PROPERTY_GAIN, "Gain" }, + { XN_STREAM_PROPERTY_HOLE_FILTER, "HoleFilter" }, + { XN_STREAM_PROPERTY_REGISTRATION_TYPE, "RegistrationType" }, + { XN_STREAM_PROPERTY_AGC_BIN, "AGCBin" }, + { XN_STREAM_PROPERTY_CONST_SHIFT, "ConstShift" }, + { XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR, "PixelSizeFactor" }, + { XN_STREAM_PROPERTY_MAX_SHIFT, "MaxShift" }, + { XN_STREAM_PROPERTY_PARAM_COEFF, "ParamCoeff" }, + { XN_STREAM_PROPERTY_SHIFT_SCALE, "ShiftScale" }, + { XN_STREAM_PROPERTY_S2D_TABLE, "S2D" }, + { XN_STREAM_PROPERTY_D2S_TABLE, "D2S" }, + { XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, "ZPD" }, + { XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, "ZPPS" }, + { XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, "LDDIS" }, + { XN_STREAM_PROPERTY_DCMOS_RCMOS_DISTANCE, "DCRCDIS" }, + { XN_STREAM_PROPERTY_CLOSE_RANGE, "CloseRange" }, + { XN_STREAM_PROPERTY_PIXEL_REGISTRATION, "PixelRegistration" }, + /*{ XN_MODULE_PROPERTY_SDK_VERSION, "SDKVersion" }, + { XN_MODULE_PROPERTY_DEVICE_NAME, "DeviceName" }, + { XN_MODULE_PROPERTY_USB_PATH, "USBPath" }, + { XN_MODULE_PROPERTY_USB_INTERFACE, "UsbInterface" }, + { XN_MODULE_PROPERTY_RESET_SENSOR_ON_STARTUP, "ResetSensorOnStartup" }, + { XN_MODULE_PROPERTY_LEAN_INIT, "LeanInit" }, + { XN_MODULE_PROPERTY_ID, "ID" }, + { XN_MODULE_PROPERTY_VERSION, "Version" }, + */ +}; + +PlayerDevice::PlayerDevice(const xnl::String& filePath) : + m_filePath(filePath), m_fileHandle(0), m_threadHandle(NULL), m_running(FALSE), m_isSeeking(FALSE), + m_dPlaybackSpeed(1.0), m_nStartTimestamp(0), m_nStartTime(0), m_bHasTimeReference(FALSE), + m_bRepeat(TRUE), m_player(filePath.Data()), m_driverEOFCallback(NULL), m_driverCookie(NULL) +{ + // Create the events. + m_readyForDataInternalEvent.Create(FALSE); + m_manualTriggerInternalEvent.Create(FALSE); + m_SeekCompleteInternalEvent.Create(FALSE); +} + +PlayerDevice::~PlayerDevice() +{ + close(); +} + +OniStatus PlayerDevice::Initialize() +{ + static XnNodeNotifications notifications = + { + OnNodeAdded, + OnNodeRemoved, + OnNodeIntPropChanged, + OnNodeRealPropChanged, + OnNodeStringPropChanged, + OnNodeGeneralPropChanged, + OnNodeStateReady, + OnNodeNewData, + }; + static XnPlayerInputStreamInterface inputInterface = + { + FileOpen, + FileRead, + FileSeek, + FileTell, + FileClose, + FileSeek64, + FileTell64, + }; + static PlayerNode::CodecFactory codecFactory = + { + CodecCreate, + CodecDestroy + }; + + // Initialize the player. + XnStatus rc = m_player.Init(); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + // Set the notifications. + rc = m_player.SetNodeNotifications(this, ¬ifications); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + // Set the codec factory. + rc = m_player.SetNodeCodecFactory(this, &codecFactory); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + // Register to end of file reached event. + XnCallbackHandle handle; + rc = m_player.RegisterToEndOfFileReached(OnEndOfFileReached, this, handle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + // Set the input interface. + rc = m_player.SetInputStream(this, &inputInterface); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + // Create thread for running the player. + XnStatus status = xnOSCreateThread(ThreadProc, this, &m_threadHandle); + if (status != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + return ONI_STATUS_OK; +} + +void PlayerDevice::close() +{ + // Destroy the thread. + m_running = false; + m_readyForDataInternalEvent.Set(); + m_manualTriggerInternalEvent.Set(); + XnStatus rc = xnOSWaitForThreadExit(m_threadHandle, DEVICE_DESTROY_THREAD_TIMEOUT); + if (rc != XN_STATUS_OK) + { + xnOSTerminateThread(&m_threadHandle); + } + else + { + xnOSCloseThread(&m_threadHandle); + } + + // Destroy the player. + m_player.Destroy(); + + // Delete all the sources and streams. + Lock(); + while (m_streams.Begin() != m_streams.End()) + { + PlayerStream* pStream = *m_streams.Begin(); + m_streams.Remove(pStream); + } + while (m_sources.Begin() != m_sources.End()) + { + PlayerSource* pSource = *m_sources.Begin(); + m_sources.Remove(pSource); + XN_DELETE(pSource); + } + Unlock(); +} + +OniStatus PlayerDevice::getSensorInfoList(OniSensorInfo** pSources, int* numSources) +{ + Lock(); + + // Update source count. + *numSources = (int)m_sources.Size(); + *pSources = XN_NEW_ARR(OniSensorInfo, *numSources); + + // Copy sources. + SourceList::Iterator iter = m_sources.Begin(); + for (int i = 0; i < *numSources; ++i, ++iter) + { + xnOSMemCopy(&(*pSources)[i], (*iter)->GetInfo(), sizeof(OniSensorInfo)); + } + + Unlock(); + + return ONI_STATUS_OK; +} + +driver::StreamBase* PlayerDevice::createStream(OniSensorType sensorType) +{ + // Find the requested source. + Lock(); + PlayerSource* pSource = NULL; + for (SourceList::Iterator iter = m_sources.Begin(); iter != m_sources.End(); ++iter) + { + if ((*iter)->GetInfo()->sensorType == sensorType) + { + pSource = (*iter); + break; + } + } + Unlock(); + + // Check if source was found. + if (pSource == NULL) + { + return NULL; + } + + // Create a new stream using the source. + PlayerStream* pStream = XN_NEW(PlayerStream, pSource); + if (pStream == NULL) + { + return NULL; + } + + // Initialize the stream. + OniStatus rc = pStream->Initialize(); + if (rc != ONI_STATUS_OK) + { + XN_DELETE(pStream); + return NULL; + } + + Lock(); + + XnStatus xnrc = m_streams.AddLast(pStream); + if (xnrc != XN_STATUS_OK) + { + Unlock(); + XN_DELETE(pStream); + return NULL; + } + + // Register to ready for data event. + // NOTE: handle is discarded, as device will always exist longer than stream, therefore device can never unregister. + OniCallbackHandle handle; + rc = pStream->RegisterReadyForDataEvent(ReadyForDataCallback, this, handle); + if (rc != ONI_STATUS_OK) + { + m_streams.Remove(pStream); + Unlock(); + XN_DELETE(pStream); + return NULL; + } + + // Register to stream destroy event. + // NOTE: handle is discarded, as device will always exist longer than stream, therefore device can never unregister. + rc = pStream->RegisterDestroyEvent(StreamDestroyCallback, this, handle); + if (rc != ONI_STATUS_OK) + { + m_streams.Remove(pStream); + Unlock(); + XN_DELETE(pStream); + return NULL; + } + + Unlock(); + + return pStream; +} + +void PlayerDevice::destroyStream(oni::driver::StreamBase* pStream) +{ + m_streams.Remove((PlayerStream*)pStream); + XN_DELETE(pStream); +} + +OniStatus PlayerDevice::tryManualTrigger() +{ + // Set and reset the trigger. + XnStatus rc = m_manualTriggerInternalEvent.Set(); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + return ONI_STATUS_OK; +} + +/// Get property. +OniStatus PlayerDevice::getProperty(int propertyId, void* data, int* pDataSize) +{ + OniStatus rc = ONI_STATUS_OK; + + // Check if property requires special handling. + if (propertyId == ONI_DEVICE_PROPERTY_PLAYBACK_SPEED) + { + // Verify data size. + if (*pDataSize != sizeof(float)) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Return the playback speed. + *((float*)data) = (float)m_dPlaybackSpeed; + } + else if (propertyId == ONI_DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED) + { + // Validate parameter size. + if (*pDataSize != sizeof(OniBool)) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Return the repeat value. + *((OniBool*)data) = m_bRepeat; + } + else + { + // Get the property. + Lock(); + rc = m_properties.GetProperty(propertyId, data, pDataSize); + Unlock(); + } + + return rc; +} + +/// Set property. +OniStatus PlayerDevice::setProperty(int propertyId, const void* data, int dataSize) +{ + OniStatus rc = ONI_STATUS_OK; + + // Check if property requires special handling. + if (propertyId == ONI_DEVICE_PROPERTY_PLAYBACK_SPEED) + { + // Validate parameter size. + if (dataSize != sizeof(float)) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Update the playback speed. + m_dPlaybackSpeed = (double)*((float*)data); + + // Reset the timing reference. + m_bHasTimeReference = FALSE; + } + else if (propertyId == ONI_DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED) + { + // Validate parameter size. + if (dataSize != sizeof(OniBool)) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Update the repeat. + m_bRepeat = *((OniBool*)data); + m_player.SetRepeat(m_bRepeat); + } + else + { + // Set the property. + Lock(); + rc = m_properties.SetProperty(propertyId, data, dataSize); + Unlock(); + } + + return rc; +} + +OniBool PlayerDevice::isPropertySupported(int propertyId) +{ + return propertyId == ONI_DEVICE_PROPERTY_PLAYBACK_SPEED || + propertyId == ONI_DEVICE_PROPERTY_PLAYBACK_REPEAT_ENABLED || + m_properties.Exists(propertyId); +} + +/// @copydoc OniDeviceBase::Invoke(int, const void*, int) +OniStatus PlayerDevice::invoke(int commandId, void* data, int dataSize) +{ + if (commandId == ONI_DEVICE_COMMAND_SEEK) + { + if (m_player.IsEOF()) + { + return ONI_STATUS_ERROR; + } + + // Verify data size. + if (dataSize != sizeof(Seek)) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Seek the frame ID for all sources. + Seek* pSeek = (Seek*)data; + m_seek.frameId = pSeek->frameId; + m_seek.pStream = pSeek->pStream; + m_isSeeking = TRUE; + + // Set the ready for data and manual trigger events, to make sure player thread wakes up. + m_readyForDataInternalEvent.Set(); + m_manualTriggerInternalEvent.Set(); + + // Wait for seek to complete. + m_SeekCompleteInternalEvent.Wait(XN_WAIT_INFINITE); + } + else + { + return ONI_STATUS_NOT_IMPLEMENTED; + } + + return ONI_STATUS_OK; +} + +OniBool PlayerDevice::isCommandSupported(int commandId) +{ + return commandId == ONI_DEVICE_COMMAND_SEEK; +} + +PlayerSource* PlayerDevice::FindSource(const XnChar* strNodeName) +{ + Lock(); + + // Find the relevant source. + for (SourceList::Iterator iter = m_sources.Begin(); iter != m_sources.End(); ++iter) + { + if (strcmp((*iter)->GetNodeName(), strNodeName) == 0) + { + PlayerSource* pSource = *iter; + Unlock(); + return pSource; + } + } + + Unlock(); + + return NULL; +} + +void PlayerDevice::SleepToTimestamp(XnUInt64 nTimeStamp) +{ + XnUInt64 nNow; + xnOSGetHighResTimeStamp(&nNow); + + m_cs.Lock(); + + XnBool bHasTimeReference = TRUE; + if (!m_bHasTimeReference /*&& (nTimeStamp <= m_nStartTimestamp)*/) + { + m_nStartTimestamp = nTimeStamp; + m_nStartTime = nNow; + + m_bHasTimeReference = TRUE; + bHasTimeReference = FALSE; + } + + m_cs.Unlock(); + + if (bHasTimeReference && (m_dPlaybackSpeed > 0.0f)) + { + // check this data timestamp compared to when we started + XnInt64 nTimestampDiff = nTimeStamp - m_nStartTimestamp; + + // in some recordings, frames are not ordered by timestamp. Make sure this does not break the mechanism + if (nTimestampDiff > 0) + { + XnInt64 nTimeDiff = nNow - m_nStartTime; + + // check if we need to wait some time + XnInt64 nRequestedTimeDiff = (XnInt64)(nTimestampDiff / m_dPlaybackSpeed); + if (nTimeDiff < nRequestedTimeDiff) + { + XnUInt32 nSleep = XnUInt32((nRequestedTimeDiff - nTimeDiff)/1000); + nSleep = XN_MIN(nSleep, XN_PLAYBACK_SPEED_SANITY_SLEEP); + xnOSSleep(nSleep); + } + + // update reference to current frame (this will handle cases in which application + // stopped reading frames and continued after a while) + m_nStartTimestamp = nTimeStamp; + xnOSGetHighResTimeStamp(&m_nStartTime); + } + } +} + +void PlayerDevice::MainLoop() +{ + m_running = true; + while (m_running) + { + // Process data only when at least one of the streams within has started + bool waitForStreamStart = true; + for (StreamList::Iterator iter = m_streams.Begin(); iter != m_streams.End(); iter++) + { + PlayerStream* pStream = *iter; + if (pStream->isStreamStarted()) + { + waitForStreamStart = false; + break; + } + } + if (waitForStreamStart) + { + xnOSSleep(10); + continue; + } + + if (m_isSeeking) + { + // Set playback speed to maximum. + double playbackSpeed = m_dPlaybackSpeed; + m_dPlaybackSpeed = XN_PLAYBACK_SPEED_FASTEST; + + // Seek the frame ID for first source (seek to (frame ID-1) so next read frame is frameId). + PlayerSource* pSource = m_seek.pStream->GetSource(); + XnStatus xnrc = m_player.SeekToFrame(pSource->GetNodeName(), m_seek.frameId, XN_PLAYER_SEEK_SET); + if (xnrc != XN_STATUS_OK) + { + // Failure to seek. + m_isSeeking = FALSE; + continue; + } + + // Return playback speed to normal. + m_dPlaybackSpeed = playbackSpeed; + + // Reset the wait events. + m_readyForDataInternalEvent.Reset(); + m_manualTriggerInternalEvent.Reset(); + + // Reset the time reference. + m_bHasTimeReference = FALSE; + + // Raise the seek complete event. + m_SeekCompleteInternalEvent.Set(); + + // Mark the seeking flag as false. + m_isSeeking = FALSE; + } + else + { + // Read the next frame (delay between frames must be dealt with in the OnNodeNewData callback). + m_player.ReadNext(); + } + } +} + +XN_THREAD_PROC PlayerDevice::ThreadProc(XN_THREAD_PARAM pThreadParam) +{ + PlayerDevice* pThis = reinterpret_cast(pThreadParam); + pThis->MainLoop(); + + XN_THREAD_PROC_RETURN(XN_STATUS_OK); +} + +void ONI_CALLBACK_TYPE PlayerDevice::ReadyForDataCallback(const PlayerStream::ReadyForDataEventArgs& /*newDataEventArgs*/, void* pCookie) +{ + PlayerDevice* pThis = (PlayerDevice*)(pCookie); + pThis->m_readyForDataInternalEvent.Set(); +} + +void ONI_CALLBACK_TYPE PlayerDevice::StreamDestroyCallback(const PlayerStream::DestroyEventArgs& destroyEventArgs, void* pCookie) +{ + PlayerDevice* pThis = (PlayerDevice*)(pCookie); + pThis->Lock(); + pThis->m_streams.Remove(destroyEventArgs.pStream); + pThis->Unlock(); +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeAdded(void* pCookie, const XnChar* strNodeName, XnProductionNodeType type, XnCodecID /*compression*/, XnUInt32 nNumberOfFrames) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + + // Check node type. + switch (type) + { + case XN_NODE_TYPE_DEVICE: + { + // Store the node name. + pThis->m_nodeName = strNodeName; + break; + } + case XN_NODE_TYPE_DEPTH: + case XN_NODE_TYPE_IMAGE: + case XN_NODE_TYPE_IR: + { + // Check if the source already exists. + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource == NULL) + { + // Create the new source. + OniSensorType sensorType = (type == XN_NODE_TYPE_DEPTH) ? ONI_SENSOR_DEPTH : + (type == XN_NODE_TYPE_IMAGE) ? ONI_SENSOR_COLOR : ONI_SENSOR_IR; + pSource = XN_NEW(PlayerSource, strNodeName, sensorType); + if (pSource == NULL) + { + return XN_STATUS_ERROR; + } + pSource->SetProperty(ONI_STREAM_PROPERTY_NUMBER_OF_FRAMES, &nNumberOfFrames, sizeof(int)); + + // Add the source. + pThis->Lock(); + pThis->m_sources.AddLast(pSource); + pThis->Unlock(); + } + + break; + } + /*case XN_NODE_TYPE_AUDIO: + { + break; + }*/ + default: + { + + } + } + + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeRemoved(void* /*pCookie*/, const XnChar* /*strNodeName*/) +{ + /*PlayerDevice* pThis = (PlayerDevice*)pCookie; + + // Remove the source. + pThis->Lock(); + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource != NULL) + { + pThis->m_sources.Remove(pSource); + XN_DELETE(pSource); + } + pThis->Unlock(); + + return (pSource != NULL) ? XN_STATUS_OK : XN_STATUS_NO_MATCH;*/ + + // Do not remove the node. + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeIntPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, XnUInt64 nValue) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + XnStatus nRetVal = XN_STATUS_OK; + OniStatus rc; + + // Find the source. + pThis->Lock(); + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource != NULL) + { + // find the relevant property ID for the node. + if (strcmp(strPropName, XN_PROP_DEVICE_MAX_DEPTH) == 0) + { + int oniValue = (int)nValue; + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_MAX_VALUE, &oniValue, sizeof(oniValue)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + } + else if (strcmp(strPropName, XN_PROP_BYTES_PER_PIXEL) == 0) + { + // TODO: update stride. + //ONI_STREAM_PROPERTY_STRIDE + } + else if (strcmp(strPropName, XN_PROP_MIRROR) == 0) + { + OniBool oniValue = (OniBool)nValue; + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_MIRRORING, &oniValue, sizeof(oniValue)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + } + else if (strcmp(strPropName, XN_PROP_PIXEL_FORMAT) == 0) + { + OniVideoMode videoMode; + + // Get the previous property. + int dataSize = sizeof(videoMode); + pSource->GetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, &dataSize); + + // Update the video mode value and set the property. + if (pSource->GetInfo()->sensorType == ONI_SENSOR_DEPTH) + { + videoMode.pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + } + else + { + switch (nValue) + { + case XN_PIXEL_FORMAT_RGB24: + videoMode.pixelFormat = ONI_PIXEL_FORMAT_RGB888; + break; + case XN_PIXEL_FORMAT_YUV422: + videoMode.pixelFormat = ONI_PIXEL_FORMAT_YUV422; + break; + case XN_PIXEL_FORMAT_GRAYSCALE_8_BIT: + videoMode.pixelFormat = ONI_PIXEL_FORMAT_GRAY8; + break; + case XN_PIXEL_FORMAT_GRAYSCALE_16_BIT: + videoMode.pixelFormat = ONI_PIXEL_FORMAT_GRAY16; + break; + case XN_PIXEL_FORMAT_MJPEG: + videoMode.pixelFormat = ONI_PIXEL_FORMAT_JPEG; + break; + default: + nRetVal = XN_STATUS_BAD_PARAM; + break; + } + } + + if (nRetVal != XN_STATUS_BAD_PARAM) + { + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, sizeof(videoMode)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + } + } + else if (strcmp(strPropName, XN_PROP_ONI_PIXEL_FORMAT) == 0) + { + // Get the previous property. + OniVideoMode videoMode; + int dataSize = sizeof(videoMode); + pSource->GetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, &dataSize); + videoMode.pixelFormat = (OniPixelFormat)nValue; + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, sizeof(videoMode)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + } + else if (strcmp(strPropName, XN_PROP_ONI_REQUIRED_FRAME_SIZE) == 0 || strcmp(strPropName, "RequiredDataSize") == 0) + { + pSource->SetRequiredFrameSize((int)nValue); + } + else + { + nRetVal = pThis->AddPrivateProperty(pSource, strPropName, sizeof(nValue), &nValue); + } + } + pThis->Unlock(); + + return nRetVal; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeRealPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, XnDouble dValue) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + XnStatus nRetVal = XN_STATUS_OK; + + // Find the source. + pThis->Lock(); + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource != NULL) + { + nRetVal = pThis->AddPrivateProperty(pSource, strPropName, sizeof(dValue), &dValue); + } + pThis->Unlock(); + + return nRetVal; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeStringPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, const XnChar* strValue) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + XnStatus nRetVal = XN_STATUS_OK; + + // Find the source. + pThis->Lock(); + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource != NULL) + { + nRetVal = pThis->AddPrivateProperty(pSource, strPropName, (XnUInt32)strlen(strValue)+1, strValue); + } + pThis->Unlock(); + + return nRetVal; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeGeneralPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, XnUInt32 nBufferSize, const void* pBuffer) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + XnStatus nRetVal = XN_STATUS_OK; + OniStatus rc; + + // Find the source. + pThis->Lock(); + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource != NULL) + { + // find the relevant property ID for the node. + if (strcmp(strPropName, XN_PROP_CROPPING) == 0) + { + if (nBufferSize != sizeof(XnCropping)) + { + nRetVal = XN_STATUS_BAD_PARAM; + } + else + { + // Convert the XnCropping structure to OniCropping. + XnCropping* pCropping = (XnCropping*)pBuffer; + OniCropping cropping; + cropping.enabled = pCropping->bEnabled; + cropping.originX = pCropping->nXOffset; + cropping.originY = pCropping->nYOffset; + cropping.width = pCropping->nXSize; + cropping.height = pCropping->nYSize; + + // Set the property. + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_CROPPING, &cropping, sizeof(cropping)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + } + } + else if (strcmp(strPropName, XN_PROP_MAP_OUTPUT_MODE) == 0) + { + if (nBufferSize != sizeof(XnMapOutputMode)) + { + nRetVal = XN_STATUS_BAD_PARAM; + } + else + { + XnMapOutputMode* pMapOutputMode = (XnMapOutputMode*)pBuffer; + OniVideoMode videoMode; + + // Get the previous property (to retain the existing format). + int dataSize = sizeof(videoMode); + //int bpp = 2; + rc = pSource->GetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, &dataSize); + if (rc != ONI_STATUS_OK) + { + // Set default values. + // NOTE: those defaults better be overridden by an XN_PROP_PIXEL_FORMAT, or bugs may occur in the non-default cases! + OniSensorType sensorType = pSource->GetInfo()->sensorType; + switch (sensorType) + { + case ONI_SENSOR_DEPTH: + { + videoMode.pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + //bpp = 2; + break; + } + case ONI_SENSOR_COLOR: + { + videoMode.pixelFormat = ONI_PIXEL_FORMAT_RGB888; + //bpp = 3; + break; + } + case ONI_SENSOR_IR: + { + videoMode.pixelFormat = ONI_PIXEL_FORMAT_GRAY16; + //bpp = 2; + break; + } + default: + { + return XN_STATUS_BAD_PARAM; + } + } + } + + // Convert the XnMapOutputMode structure to OniVideoMode. + videoMode.resolutionX = pMapOutputMode->nXRes; + videoMode.resolutionY = pMapOutputMode->nYRes; + videoMode.fps = pMapOutputMode->nFPS; + + // Set the property. + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, sizeof(videoMode)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + } + } + else if (strcmp(strPropName, XN_PROP_FIELD_OF_VIEW) == 0) + { + XnFieldOfView* pFieldOfView = (XnFieldOfView*)pBuffer; + + // Set the HFOV. + float fov = (float)pFieldOfView->fHFOV; + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_HORIZONTAL_FOV, &fov, sizeof(fov)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + else + { + // Set the VFOV. + fov = (float)pFieldOfView->fVFOV; + rc = pSource->SetProperty(ONI_STREAM_PROPERTY_VERTICAL_FOV, &fov, sizeof(fov)); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + } + } + else + { + nRetVal = pThis->AddPrivateProperty(pSource, strPropName, nBufferSize, pBuffer); + } + } + pThis->Unlock(); + + return nRetVal; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeStateReady(void* /*pCookie*/, const XnChar* /*strNodeName*/) +{ + // Ignore + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::OnNodeNewData(void* pCookie, const XnChar* strNodeName, XnUInt64 nTimeStamp, XnUInt32 nFrame, const void* pData, XnUInt32 nSize) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + + // Ignore zero frame (may be returned in case of seek error). + if ((nTimeStamp == 0) && (nFrame == 0)) + { + return XN_STATUS_OK; + } + + // Find the relevant source. + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource != NULL) + { + // Make sure streams are ready to receive the frame. + OniBool ready = FALSE; + OniBool hasStreams = TRUE; + while (hasStreams && !ready && pThis->m_running) + { + // Check if any stream is ready to receive the frames. + // NOTE: all the streams have a local 'last frame' buffer, so worst case other streams on source will buffer the frame. + pThis->Lock(); + hasStreams = FALSE; + for (StreamList::Iterator iter = pThis->m_streams.Begin(); iter != pThis->m_streams.End(); iter++) + { + PlayerStream* pStream = *iter; + if (pStream->GetSource() == pSource) + { + hasStreams = TRUE; + ready = TRUE; + break; + } + } + pThis->Unlock(); + + // If no ready device found, wait for ready for data event. + if (hasStreams) + { + if (ready) + { + // Check if waiting for manual trigger (playback speed is zero). + if (pThis->m_dPlaybackSpeed == XN_PLAYBACK_SPEED_MANUAL) + { + // Wait for manual trigger. + XnStatus rc = pThis->m_manualTriggerInternalEvent.Wait(DEVICE_MANUAL_TRIGGER_STANITY_SLEEP); + if (rc == XN_STATUS_OK) + { + pThis->m_manualTriggerInternalEvent.Reset(); + } + else + { + ready = FALSE; + } + } + } + else + { + // Wait for streams to become ready. + pThis->m_readyForDataInternalEvent.Wait(DEVICE_READY_FOR_DATA_EVENT_SANITY_SLEEP); + } + } + } + + // Sleep until next timestamp has expired. + pThis->SleepToTimestamp(nTimeStamp); + + // Continue processing in the source. + void* data = const_cast(pData); + pSource->ProcessNewData(nTimeStamp, nFrame, data, nSize); + } + + return XN_STATUS_OK; +} + +void XN_CALLBACK_TYPE PlayerDevice::OnEndOfFileReached(void* pCookie) +{ + // Reset time reference for all streams. + PlayerDevice* pThis = (PlayerDevice*)pCookie; + pThis->Lock(); + pThis->m_bHasTimeReference = FALSE; + pThis->Unlock(); + + // Notify the driver in case the player has finished playing (no-rewind) + if (pThis->isPlayerEOF()) + { + pThis->TriggerDriverEOFCallback(); + } +} + +XnStatus PlayerDevice::AddPrivateProperty(PlayerSource* pSource, const XnChar* strPropName, XnUInt32 nBufferSize, const void* pBuffer) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // Find the property name in the PS1080 properties. + int numProperties = ARRAYSIZE(PS1080PropertyList); + for (int i = 0; i < numProperties; ++i) + { + if (strcmp(strPropName, PS1080PropertyList[i].propertyName) == 0) + { + OniStatus rc = pSource->SetProperty(PS1080PropertyList[i].propertyId, pBuffer, nBufferSize); + if (rc != ONI_STATUS_OK) + { + nRetVal = XN_STATUS_ERROR; + } + break; + } + } + + return nRetVal; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::FileOpen(void* pCookie) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + return xnOSOpenFile(pThis->m_filePath.Data(), XN_OS_FILE_READ, &pThis->m_fileHandle); +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::FileRead(void* pCookie, void* pBuffer, XnUInt32 nSize, XnUInt32* pnBytesRead) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + XnUInt32 bufferSize = nSize; + XnStatus rc = xnOSReadFile(pThis->m_fileHandle, pBuffer, &bufferSize); + *pnBytesRead = bufferSize; + return rc; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::FileSeek(void* pCookie, XnOSSeekType seekType, const XnInt32 nOffset) +{ + return PlayerDevice::FileSeek64(pCookie, seekType, nOffset); +} + +XnUInt32 XN_CALLBACK_TYPE PlayerDevice::FileTell(void* pCookie) +{ + return (XnUInt32)PlayerDevice::FileTell64(pCookie); +} + +void XN_CALLBACK_TYPE PlayerDevice::FileClose(void* pCookie) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + xnOSCloseFile(&pThis->m_fileHandle); + pThis->m_fileHandle = 0; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::FileSeek64(void* pCookie, XnOSSeekType seekType, const XnInt64 nOffset) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + return xnOSSeekFile64(pThis->m_fileHandle, seekType, nOffset); +} + +XnUInt64 XN_CALLBACK_TYPE PlayerDevice::FileTell64(void* pCookie) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + XnUInt64 pos = 0xffffffff; + XnStatus rc = xnOSTellFile64(pThis->m_fileHandle, &pos); + if (rc == XN_STATUS_OK) + { + return pos; + } + return 0xffffffff; +} + +XnStatus XN_CALLBACK_TYPE PlayerDevice::CodecCreate(void* pCookie, const char* strNodeName, XnCodecID nCodecID, XnCodec** ppCodec) +{ + PlayerDevice* pThis = (PlayerDevice*)pCookie; + + // Find the relevant source. + PlayerSource* pSource = pThis->FindSource(strNodeName); + if (pSource == NULL) + { + return XN_STATUS_NO_MATCH; + } + + // Create the matching codec. + XnStatus rc = PlayerCodecFactory::Create(nCodecID, pSource, ppCodec); + XN_IS_STATUS_OK(rc); + + return XN_STATUS_OK; +} + +void XN_CALLBACK_TYPE PlayerDevice::CodecDestroy(void* /*pCookie*/, XnCodec* pCodec) +{ + // Destroy the codec. + PlayerCodecFactory::Destroy(pCodec); +} + +} // namespace oni_files_player diff --git a/Source/Drivers/OniFile/PlayerDevice.h b/Source/Drivers/OniFile/PlayerDevice.h new file mode 100644 index 0000000..2bcba8e --- /dev/null +++ b/Source/Drivers/OniFile/PlayerDevice.h @@ -0,0 +1,195 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the declaration of Device class that implements a virtual OpenNI +/// device, capable of reading data from a *.ONI file. + +#ifndef __PLAYER_DEVICE_H__ +#define __PLAYER_DEVICE_H__ + +#include "Driver/OniDriverAPI.h" +#include "XnString.h" +#include "XnList.h" +#include "XnOSCpp.h" +#include "PlayerNode.h" +#include "PlayerProperties.h" +#include "PlayerStream.h" + +namespace oni_file { + +class PlayerSource; + +/// Implements a virtual OpenNI device, which reads is adata from a *.ONI file. +class PlayerDevice : public oni::driver::DeviceBase +{ +public: + /// Constructs a device from the given file path. + /// @param[in] filePath The path to a *.ONI file. + PlayerDevice(const xnl::String& filePath); + ~PlayerDevice(); + + /// Initialize the device object. + OniStatus Initialize(); + + /// @copydoc OniDeviceBase::GetStreamSourceInfoList(OniSourceInfo**, int*) + virtual OniStatus getSensorInfoList(OniSensorInfo** pSources, int* numSources); + + /// @copydoc OniDeviceBase::CreateStream(OniStreamSource) + virtual oni::driver::StreamBase* createStream(OniSensorType); + + virtual void destroyStream(oni::driver::StreamBase* pStream); + + /// @copydoc OniDeviceBase::TryManualTrigger() + virtual OniStatus tryManualTrigger(); + + /// Get property. + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + + /// Set property. + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + + virtual OniBool isPropertySupported(int propertyId); + + /// @copydoc OniDeviceBase::Invoke(int, void*, int) + virtual OniStatus invoke(int commandId, void* data, int dataSize); + virtual OniBool isCommandSupported(int commandId); + + OniBool isPlayerEOF() { return m_player.IsEOF(); }; + + typedef void (XN_CALLBACK_TYPE *DriverEOFCallback)(void* pCookie, const char* uri); + void SetEOFEventCallback(DriverEOFCallback pFunc, void* pDriverCookie) + { + m_driverEOFCallback = pFunc; + m_driverCookie = pDriverCookie; + }; + void TriggerDriverEOFCallback() { if(m_driverEOFCallback) (m_driverEOFCallback)(m_driverCookie, m_filePath.Data()); }; + +protected: + + void Lock() { m_cs.Lock(); } + void Unlock() { m_cs.Unlock(); } + + PlayerSource* FindSource(const XnChar* strNodeName); + + // Wake up when timestamp is valid. + void SleepToTimestamp(XnUInt64 nTimeStamp); + +private: + void close(); + + typedef struct + { + int frameId; + PlayerStream* pStream; + } Seek; + + void MainLoop(); + static XN_THREAD_PROC ThreadProc(XN_THREAD_PARAM pThreadParam); + + static void ONI_CALLBACK_TYPE ReadyForDataCallback(const PlayerStream::ReadyForDataEventArgs& newDataEventArgs, void* pCookie); + static void ONI_CALLBACK_TYPE StreamDestroyCallback(const PlayerStream::DestroyEventArgs& destroyEventArgs, void* pCookie); + + static XnStatus XN_CALLBACK_TYPE OnNodeAdded(void* pCookie, const XnChar* strNodeName, XnProductionNodeType type, XnCodecID compression, XnUInt32 nNumberOfFrames); + static XnStatus XN_CALLBACK_TYPE OnNodeRemoved(void* pCookie, const XnChar* strNodeName); + static XnStatus XN_CALLBACK_TYPE OnNodeIntPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, XnUInt64 nValue); + static XnStatus XN_CALLBACK_TYPE OnNodeRealPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, XnDouble dValue); + static XnStatus XN_CALLBACK_TYPE OnNodeStringPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, const XnChar* strValue); + static XnStatus XN_CALLBACK_TYPE OnNodeGeneralPropChanged(void* pCookie, const XnChar* strNodeName, const XnChar* strPropName, XnUInt32 nBufferSize, const void* pBuffer); + static XnStatus XN_CALLBACK_TYPE OnNodeStateReady(void* pCookie, const XnChar* strNodeName); + static XnStatus XN_CALLBACK_TYPE OnNodeNewData(void* pCookie, const XnChar* strNodeName, XnUInt64 nTimeStamp, XnUInt32 nFrame, const void* pData, XnUInt32 nSize); + static void XN_CALLBACK_TYPE OnEndOfFileReached(void* pCookie); + XnStatus AddPrivateProperty(PlayerSource* pSource, const XnChar* strPropName, XnUInt32 nBufferSize, const void* pBuffer); + + static XnStatus XN_CALLBACK_TYPE FileOpen(void* pCookie); + static XnStatus XN_CALLBACK_TYPE FileRead(void* pCookie, void* pBuffer, XnUInt32 nSize, XnUInt32* pnBytesRead); + static XnStatus XN_CALLBACK_TYPE FileSeek(void* pCookie, XnOSSeekType seekType, const XnInt32 nOffset); + static XnUInt32 XN_CALLBACK_TYPE FileTell(void* pCookie); + static void XN_CALLBACK_TYPE FileClose(void* pCookie); + static XnStatus XN_CALLBACK_TYPE FileSeek64(void* pCookie, XnOSSeekType seekType, const XnInt64 nOffset); + static XnUInt64 XN_CALLBACK_TYPE FileTell64(void* pCookie); + + static XnStatus XN_CALLBACK_TYPE CodecCreate(void* pCookie, const char* strNodeName, XnCodecID nCodecId, XnCodec** ppCodec); + static void XN_CALLBACK_TYPE CodecDestroy(void* pCookie, XnCodec* pCodec); + + // Name of the node (used for identifying the device in the callbacks). + xnl::String m_nodeName; + + // The path to a *.ONI file which is mounted by this device. + const xnl::String m_filePath; + + // Handle to the opened file. + XN_FILE_HANDLE m_fileHandle; + + // Thread handle. + XN_THREAD_HANDLE m_threadHandle; + + // Running flag. + OniBool m_running; + + // Seek frame. + Seek m_seek; + OniBool m_isSeeking; + + // Speed of playback. + XnDouble m_dPlaybackSpeed; + + // Timestamps. + XnUInt64 m_nStartTimestamp; + XnUInt64 m_nStartTime; + XnBool m_bHasTimeReference; + + // Repeat recording in loop. + OniBool m_bRepeat; + + // Player object. + PlayerNode m_player; + + // Driver EOF callback + DriverEOFCallback m_driverEOFCallback; + void *m_driverCookie; + + // Properties. + PlayerProperties m_properties; + + // List of sources. + typedef xnl::List SourceList; + SourceList m_sources; + + // List of streams. + typedef xnl::List StreamList; + StreamList m_streams; + + // Internal event for stream ready for data. + xnl::OSEvent m_readyForDataInternalEvent; + + // Internal event for manual trigger (more frames requested). + xnl::OSEvent m_manualTriggerInternalEvent; + + // Internal event for seek complete. + xnl::OSEvent m_SeekCompleteInternalEvent; + + // Critical section. + xnl::CriticalSection m_cs; +}; + +} // namespace oni_files_player + +#endif //__PLAYER_DEVICE_H__ diff --git a/Source/Drivers/OniFile/PlayerDriver.cpp b/Source/Drivers/OniFile/PlayerDriver.cpp new file mode 100644 index 0000000..caa07dd --- /dev/null +++ b/Source/Drivers/OniFile/PlayerDriver.cpp @@ -0,0 +1,145 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the definition of Driver class that implements an OpenNI driver, +/// which manages virtual OpenNI devices. Those devices read their data from +/// *.ONI files. + +#include "PlayerDriver.h" +#include "XnLib.h" +#include "XnString.h" +#include "PlayerDevice.h" + +namespace oni_file { + +namespace driver = oni::driver; + +namespace { + +// A string that stores device vendor name. +const xnl::String kVendorString("PrimeSense, Ltd."); +const xnl::String kDeviceName("oni File"); + +} // namespace + +PlayerDriver::PlayerDriver(OniDriverServices* pDriverServices) + : driver::DriverBase(pDriverServices), m_fileHandle(XN_INVALID_FILE_HANDLE) +{ +} + +driver::DeviceBase* PlayerDriver::deviceOpen(const char* strUri, const char* /*mode*/) +{ + PlayerDevice* pDevice = XN_NEW(PlayerDevice, strUri); + if (pDevice == NULL) + { + return NULL; + } + + pDevice->SetEOFEventCallback(EOFReached, this); + + OniStatus rc = pDevice->Initialize(); + if (rc != ONI_STATUS_OK) + { + XN_DELETE(pDevice); + return NULL; + } + + return pDevice; +} + +void PlayerDriver::deviceClose(oni::driver::DeviceBase* pDevice) +{ + XN_DELETE(pDevice); +} + +void PlayerDriver::shutdown() +{ +} + +OniStatus PlayerDriver::tryDevice(const char* strUri) +{ + static XnPlayerInputStreamInterface inputInterface = + { + FileOpen, + FileRead, + NULL, + NULL, + FileClose, + NULL, + NULL, + }; + + // Store the file path. + m_filePath = strUri; + + XnStatus rc = PlayerNode::ValidateStream(this, &inputInterface); + if (rc == XN_STATUS_OK) + { + OniDeviceInfo* pInfo = XN_NEW(OniDeviceInfo); + xnOSMemSet(pInfo, 0, sizeof(*pInfo)); + xnOSStrCopy(pInfo->uri, strUri, ONI_MAX_STR); + xnOSStrCopy(pInfo->vendor, kVendorString.Data(), ONI_MAX_STR); + xnOSStrCopy(pInfo->name, kDeviceName.Data(), ONI_MAX_STR); + deviceConnected(pInfo); + return ONI_STATUS_OK; + } + + return DriverBase::tryDevice(strUri); +} + +XnStatus XN_CALLBACK_TYPE PlayerDriver::FileOpen(void* pCookie) +{ + PlayerDriver* pThis = (PlayerDriver*)pCookie; + return xnOSOpenFile(pThis->m_filePath.Data(), XN_OS_FILE_READ, &pThis->m_fileHandle); +} + +XnStatus XN_CALLBACK_TYPE PlayerDriver::FileRead(void* pCookie, void* pBuffer, XnUInt32 nSize, XnUInt32* pnBytesRead) +{ + PlayerDriver* pThis = (PlayerDriver*)pCookie; + XnUInt32 bufferSize = nSize; + XnStatus rc = xnOSReadFile(pThis->m_fileHandle, pBuffer, &bufferSize); + *pnBytesRead = bufferSize; + return rc; +} + +void XN_CALLBACK_TYPE PlayerDriver::FileClose(void* pCookie) +{ + PlayerDriver* pThis = (PlayerDriver*)pCookie; + xnOSCloseFile(&pThis->m_fileHandle); + pThis->m_fileHandle = 0; +} + +void XN_CALLBACK_TYPE PlayerDriver::EOFReached(void* pCookie, const char *strUri) +{ + PlayerDriver* pThis = (PlayerDriver*)pCookie; + + OniDeviceInfo* pInfo = XN_NEW(OniDeviceInfo); + xnOSMemSet(pInfo, 0, sizeof(*pInfo)); + xnOSStrCopy(pInfo->uri, strUri, ONI_MAX_STR); + xnOSStrCopy(pInfo->vendor, kVendorString.Data(), ONI_MAX_STR); + xnOSStrCopy(pInfo->name, kDeviceName.Data(), ONI_MAX_STR); + + pThis->deviceStateChanged(pInfo, ONI_DEVICE_STATE_EOF); +} + +} // namespace oni_file + +ONI_EXPORT_DRIVER(oni_file::PlayerDriver) diff --git a/Source/Drivers/OniFile/PlayerDriver.h b/Source/Drivers/OniFile/PlayerDriver.h new file mode 100644 index 0000000..0be4f3c --- /dev/null +++ b/Source/Drivers/OniFile/PlayerDriver.h @@ -0,0 +1,76 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the declaration of Driver class that implements an OpenNI driver, +/// which manages virtual OpenNI devices. Those devices read their data from +/// *.ONI files. + +#ifndef _ONIFILESPLAYER_DRIVER_H_ +#define _ONIFILESPLAYER_DRIVER_H_ 1 + +#include "Driver/OniDriverAPI.h" +#include "XnString.h" +#include "XnOSCpp.h" + +namespace oni_file { + +/// Implements an OpenNI driver, which manages virtual OpenNI devices. Those +/// devics read their data from *.ONI files. +class PlayerDriver : public oni::driver::DriverBase +{ +public: + /// @copydoc OniDriverBase::OniDriverBase(OniDriverServices*) + PlayerDriver(OniDriverServices* pDriverServices); + + /// @copydoc OniDriverBase::DeviceOpen(const char*) + /// @todo Document it. + oni::driver::DeviceBase* deviceOpen(const char* strUri, const char* mode); + + void deviceClose(oni::driver::DeviceBase* pDevice); + + /// @copydoc OniDriverBase::Shutdown() + virtual void shutdown(); + + /// Verifies that the given URI's scheme is known, and emits a message which + /// notifies OpenNI that a device has been connected. + /// @param[in] strUri The requested URI which points to a *.ONI file. + /// @returns ONI_STATUS_OK whenever the URI is supported, or ONI_STATUS_ERROR + /// when it's not. + virtual OniStatus tryDevice(const char* strUri); + +private: + + static XnStatus XN_CALLBACK_TYPE FileOpen(void* pCookie); + static XnStatus XN_CALLBACK_TYPE FileRead(void* pCookie, void* pBuffer, XnUInt32 nSize, XnUInt32* pnBytesRead); + static void XN_CALLBACK_TYPE FileClose(void* pCookie); + + static void XN_CALLBACK_TYPE EOFReached(void* pCookie, const char *strUri); + + // Handle to the opened file. + XN_FILE_HANDLE m_fileHandle; + + // Path to file. + xnl::String m_filePath; +}; + +} // namespace oni_files_player + +#endif // _ONIFILESPLAYER_DRIVER_H_ diff --git a/Source/Drivers/OniFile/PlayerNode.cpp b/Source/Drivers/OniFile/PlayerNode.cpp new file mode 100644 index 0000000..540b55e --- /dev/null +++ b/Source/Drivers/OniFile/PlayerNode.cpp @@ -0,0 +1,1806 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "PlayerNode.h" +#include "DataRecords.h" +#include "XnPropNames.h" +#include "Formats/XnCodecIDs.h" +#include "Formats/XnCodec.h" +#include +#include + +namespace oni_file { + +#define XN_MASK_OPEN_NI "" + +#ifdef PLAYER_NODE_LOG_RECORDS + template + inline static void DEBUG_LOG_RECORD(T record, const XnChar* strRecordName) + { + XnChar s[1024]; + XnUInt32 nCharsWritten = 0; + XnStatus nRetVal = record.AsString(s, sizeof(s), nCharsWritten); + XN_ASSERT(nRetVal == XN_STATUS_OK); + xnLogVerbose(XN_MASK_OPEN_NI, "--PLAYER--> %s: %s", strRecordName, s); + } +#else + #ifdef __WIN32 + #define DEBUG_LOG_RECORD(record, name) __noop + #else + #define DEBUG_LOG_RECORD(record, name) + #endif +#endif + +//--------------------------------------------------------------------------- +// Backward Compatibility Issues +//--------------------------------------------------------------------------- +#define XN_PROP_REAL_WORLD_TRANSLATION_DATA "xnRealWorldTranslationData" //general + +typedef struct XnRealWorldTranslationData +{ + XnDouble dZeroPlaneDistance; + XnDouble dPixelSizeAtZeroPlane; + XnDouble dSourceToDepthPixelRatio; +} XnRealWorldTranslationData; + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +//DATA_MAX_SIZE is set to support a resolution of 1600x1200 with 24 bits per pixel +const XnUInt64 PlayerNode::DATA_MAX_SIZE = 1600 * 1200 * 3; +const XnUInt64 PlayerNode::RECORD_MAX_SIZE = + NewDataRecordHeader::MAX_SIZE + + PlayerNode::DATA_MAX_SIZE; //Maximum data size + +const XnVersion PlayerNode::OLDEST_SUPPORTED_FILE_FORMAT_VERSION = {1, 0, 0, 4}; +const XnVersion PlayerNode::FIRST_FILESIZE64BIT_FILE_FORMAT_VERSION = {1, 0, 1, 0}; + +PlayerNode::PlayerNode(const XnChar* strName) : + m_bOpen(FALSE), + m_bIs32bitFileFormat(FALSE), + m_pRecordBuffer(NULL), + m_pUncompressedData(NULL), + m_pStreamCookie(NULL), + m_pInputStream(NULL), + m_pNotificationsCookie(NULL), + m_pNodeNotifications(NULL), + m_bRepeat(TRUE), + m_bDataBegun(FALSE), + m_bEOF(FALSE), + m_nTimeStamp(0), + m_nGlobalMaxTimeStamp(0), + m_pNodeInfoMap(NULL), + m_nMaxNodes(0), + m_aSeekTempArray(NULL) +{ + xnOSMemSet(&m_fileVersion, 0, sizeof(m_fileVersion)); + xnOSStrCopy(m_strName, strName, sizeof(m_strName)); + xnOSMemSet(&m_lastOutputMode, 0, sizeof(XnMapOutputMode)); +} + +PlayerNode::~PlayerNode() +{ + Destroy(); +} + +XnStatus PlayerNode::Init() +{ + m_pRecordBuffer = XN_NEW_ARR(XnUInt8, RECORD_MAX_SIZE); + XN_VALIDATE_ALLOC_PTR(m_pRecordBuffer); + m_pUncompressedData = XN_NEW_ARR(XnUInt8, DATA_MAX_SIZE); + XN_VALIDATE_ALLOC_PTR(m_pUncompressedData); + return XN_STATUS_OK; +} + +XnStatus PlayerNode::Destroy() +{ + CloseStream(); + //Don't verify return value - proceed anyway + + if (m_pNodeInfoMap != NULL) + { + for (XnUInt32 i = 0; i < m_nMaxNodes; i++) + { + RemovePlayerNodeInfo(i); + } + + XN_DELETE_ARR(m_pNodeInfoMap); + m_pNodeInfoMap = NULL; + } + + if (m_aSeekTempArray != NULL) + { + xnOSFree(m_aSeekTempArray); + m_aSeekTempArray = NULL; + } + + XN_DELETE_ARR(m_pRecordBuffer); + m_pRecordBuffer = NULL; + XN_DELETE_ARR(m_pUncompressedData); + m_pUncompressedData = NULL; + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SetInputStream(void *pStreamCookie, XnPlayerInputStreamInterface *pStream) +{ + XN_VALIDATE_INPUT_PTR(pStream); + m_pStreamCookie = pStreamCookie; + m_pInputStream = pStream; + XnStatus nRetVal = OpenStream(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SetNodeNotifications(void *pNotificationsCookie, XnNodeNotifications *pNodeNotifications) +{ + XN_VALIDATE_INPUT_PTR(pNodeNotifications); + m_pNotificationsCookie = pNotificationsCookie; + m_pNodeNotifications = pNodeNotifications; + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SetNodeCodecFactory(void* pPlayerNodeCodecFactoryCookie, + PlayerNode::CodecFactory* pPlayerNodeCodecFactory) +{ + XN_VALIDATE_INPUT_PTR(pPlayerNodeCodecFactory); + m_pNodeCodecFactoryCookie = pPlayerNodeCodecFactoryCookie; + m_pNodeCodecFactory = pPlayerNodeCodecFactory; + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SetRepeat(XnBool bRepeat) +{ + m_bRepeat = bRepeat; + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SeekToTimeStamp(XnInt64 /*nTimeOffset*/, XnPlayerSeekOrigin /*origin*/) +{ + /* + switch (origin) + { + case XN_PLAYER_SEEK_SET: + return SeekToTimeStampAbsolute((XnUInt64)nTimeOffset); + case XN_PLAYER_SEEK_CUR: + return SeekToTimeStampRelative(nTimeOffset); + case XN_PLAYER_SEEK_END: + return SeekToTimeStampAbsolute(m_nGlobalMaxTimeStamp); + default: + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_BAD_PARAM, XN_MASK_OPEN_NI, "Invalid seek origin: %u", origin); + }*/ + return XN_STATUS_NOT_IMPLEMENTED; +} + +XnStatus PlayerNode::SeekToFrame(const XnChar* strNodeName, XnInt32 nFrameOffset, XnPlayerSeekOrigin origin) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nNodeID = GetPlayerNodeIDByName(strNodeName); + if (nNodeID == INVALID_NODE_ID) + { + XN_LOG_ERROR_RETURN(XN_STATUS_BAD_NODE_NAME, XN_MASK_OPEN_NI, "Bad node name '%s'", strNodeName); + } + + PlayerNodeInfo* pPlayerNodeInfo = &m_pNodeInfoMap[nNodeID]; + + XnInt64 nOriginFrame = 0; + switch (origin) + { + case XN_PLAYER_SEEK_SET: + { + nOriginFrame = 0; + break; + } + case XN_PLAYER_SEEK_CUR: + { + nOriginFrame = pPlayerNodeInfo->nCurFrame; + break; + } + case XN_PLAYER_SEEK_END: + { + nOriginFrame = pPlayerNodeInfo->nFrames; + break; + } + default: + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_BAD_PARAM, XN_MASK_OPEN_NI, "Invalid seek origin: %u", origin); + } + } + XnUInt32 nDestFrame = (XnUInt32)XN_MIN(XN_MAX(1, nOriginFrame + nFrameOffset), pPlayerNodeInfo->nFrames); + nRetVal = SeekToFrameAbsolute(nNodeID, nDestFrame); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::UndoRecord(PlayerNode::RecordUndoInfo& undoInfo, XnUInt64 nDestPos, XnBool& bUndone) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt64 nOriginalPos = TellStream(); + bUndone = FALSE; + Record record(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + while ((undoInfo.nRecordPos > nDestPos) && (undoInfo.nUndoRecordPos != 0)) + { + nRetVal = SeekStream(XN_OS_SEEK_SET, undoInfo.nUndoRecordPos); + XN_IS_STATUS_OK(nRetVal); + nRetVal = ReadRecordHeader(record); + XN_IS_STATUS_OK(nRetVal); + undoInfo.nRecordPos = undoInfo.nUndoRecordPos; + undoInfo.nUndoRecordPos = record.GetUndoRecordPos(); + } + + if (undoInfo.nRecordPos <= nDestPos) + { + /*We found a record that can undo the record originally pointed to by undoInfo.nDestRecordPos, + so now we handle it. */ + nRetVal = ReadRecordFields(record); + XN_IS_STATUS_OK(nRetVal); + nRetVal = HandleRecord(record, FALSE); + XN_IS_STATUS_OK(nRetVal); + bUndone = TRUE; + } + else + { + nRetVal = SeekStream(XN_OS_SEEK_SET, nOriginalPos); + XN_IS_STATUS_OK(nRetVal); + } + return XN_STATUS_OK; +} + +DataIndexEntry* PlayerNode::FindTimestampInDataIndex(XnUInt32 nNodeID, XnUInt64 nTimestamp) +{ + XN_ASSERT((nNodeID != INVALID_NODE_ID) && (nNodeID < m_nMaxNodes)); + PlayerNodeInfo* pPlayerNodeInfo = &m_pNodeInfoMap[nNodeID]; + + // perform binary search. We're looking for the highest timestamp BEFORE searched timestamp + int first = 1; + int last = pPlayerNodeInfo->nFrames; + int mid; + XnUInt64 nMidTimestamp; + + while (first <= last) + { + mid = (first + last) / 2; + nMidTimestamp = pPlayerNodeInfo->pDataIndex[mid].nTimestamp; + + if (nMidTimestamp > nTimestamp) + { + last = mid - 1; + } + else if (nMidTimestamp < nTimestamp) + { + first = mid + 1; + } + else // equals + { + break; + } + } + + return &pPlayerNodeInfo->pDataIndex[first-1]; +} + +DataIndexEntry** PlayerNode::GetSeekLocationsFromDataIndex(XnUInt32 nNodeID, XnUInt32 nDestFrame) +{ + PlayerNodeInfo* pPlayerNodeInfo = &m_pNodeInfoMap[nNodeID]; + if (pPlayerNodeInfo->pDataIndex == NULL) + { + xnLogVerbose(XN_MASK_OPEN_NI, "Slow seek being used (recording doesn't have seek tables)"); + return NULL; + } + + DataIndexEntry* pCurrentFrame = &pPlayerNodeInfo->pDataIndex[pPlayerNodeInfo->nCurFrame]; + DataIndexEntry* pDestFrame = &pPlayerNodeInfo->pDataIndex[nDestFrame]; + + if (pCurrentFrame->nConfigurationID != pDestFrame->nConfigurationID) + { + // can't use fast seek. We'll have to do it the old fashion way + xnLogVerbose(XN_MASK_OPEN_NI, "Seeking from %u to %u: Slow seek being used (configuration was changed between source and destination frames)", pPlayerNodeInfo->nCurFrame, nDestFrame); + return NULL; + } + + m_aSeekTempArray[nNodeID] = pDestFrame; + + // find corresponding frames of other nodes + for (XnUInt32 i = 0; i < m_nMaxNodes; ++i) + { + if (m_pNodeInfoMap[i].bIsGenerator && i != nNodeID) + { + m_aSeekTempArray[i] = FindTimestampInDataIndex(i, pDestFrame->nTimestamp); + if (m_aSeekTempArray[i] != NULL && m_aSeekTempArray[i]->nConfigurationID != pCurrentFrame->nConfigurationID) + { + xnLogVerbose(XN_MASK_OPEN_NI, "Seeking from %u to %u: Slow seek being used (configuration was changed between source and destination frames or other nodes)", pPlayerNodeInfo->nCurFrame, nDestFrame); + return NULL; + } + } + } + + // if we got here, all nodes are in the same configuration ID, and we can perform fast seek + return m_aSeekTempArray; +} + +XnStatus PlayerNode::SeekToFrameAbsolute(XnUInt32 nNodeID, XnUInt32 nDestFrame) +{ + XN_ASSERT((nNodeID != INVALID_NODE_ID) && (nNodeID < m_nMaxNodes)); + PlayerNodeInfo* pPlayerNodeInfo = &m_pNodeInfoMap[nNodeID]; + XN_ASSERT((nDestFrame > 0) && (nDestFrame <= pPlayerNodeInfo->nFrames)); + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + + XnStatus nRetVal = XN_STATUS_OK; + + if (nDestFrame == pPlayerNodeInfo->nCurFrame) + { + //Just go back to position of current frame + nRetVal = SeekStream(XN_OS_SEEK_SET, pPlayerNodeInfo->nLastDataPos); + XN_IS_STATUS_OK(nRetVal); + // and re-read it + nRetVal = ReadNext(); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; + } + + // not same frame. Find seek locations of each stream + DataIndexEntry** pDataIndex = GetSeekLocationsFromDataIndex(nNodeID, nDestFrame); + if (pDataIndex != NULL) + { + XnUInt64 nLastPos = 0; + + // move each node to its relevant data + for (XnUInt32 i = 0; i < m_nMaxNodes; i++) + { + if (m_aSeekTempArray[i] != NULL) + { + // read data + nRetVal = SeekStream(XN_OS_SEEK_SET, m_aSeekTempArray[i]->nSeekPos); + XN_IS_STATUS_OK(nRetVal); + nRetVal = ReadNext(); + XN_IS_STATUS_OK(nRetVal); + + // check for latest position. This will be directly after the frame we seeked to. + XnUInt64 nPos = TellStream(); + if (nPos > nLastPos) + { + nLastPos = nPos; + } + } + } + + // now seek to directly after last position + SeekStream(XN_OS_SEEK_SET, nLastPos); + } + else + { + // perform old seek (no data indexes) + XnUInt64 nStartPos = TellStream(); + XnUInt32 nNextFrame = pPlayerNodeInfo->nCurFrame + 1; + XnStatus nRetVal = XN_STATUS_OK; + + if (nDestFrame < nNextFrame) + { + //Seek backwards + XnUInt64 nDestRecordPos = pPlayerNodeInfo->newDataUndoInfo.nRecordPos; + XnUInt64 nUndoRecordPos = pPlayerNodeInfo->newDataUndoInfo.nUndoRecordPos; + NewDataRecordHeader record(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + + /*Scan back through the frames' undo positions until we get to a frame number that is smaller or equal + to nDestFrame. We put the position of the frame we find in nDestRecordPos. */ + do + { + if (nUndoRecordPos == 0) + { + /* The last frame we encountered doesn't have an undo frame. But this data frame can't be the first, + so the file is corrupt */ + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Undo frame not found for frame in position %u", nDestRecordPos); + } + nRetVal = SeekStream(XN_OS_SEEK_SET, nUndoRecordPos); + XN_IS_STATUS_OK(nRetVal); + nDestRecordPos = nUndoRecordPos; + record.ResetRead(); + nRetVal = ReadRecordHeader(record); + XN_IS_STATUS_OK(nRetVal); + if (record.GetType() != RECORD_NEW_DATA) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Unexpected record type: %u", record.GetType()); + } + + if (record.GetNodeID() != nNodeID) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Unexpected node id: %u", record.GetNodeID()); + } + + nRetVal = ReadRecordFields(record); + XN_IS_STATUS_OK(nRetVal); + nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + nUndoRecordPos = record.GetUndoRecordPos(); + } while (record.GetFrameNumber() > nDestFrame); + + //Now handle the frame + nRetVal = HandleNewDataRecord(record, FALSE); + XnBool bUndone = FALSE; + + for (XnUInt32 i = 0; i < m_nMaxNodes; ++i) + { + //Rollback all properties to match the state the stream was in at position nDestRecordPos + PlayerNodeInfo &pni = m_pNodeInfoMap[i]; + for (RecordUndoInfoMap::Iterator it = pni.recordUndoInfoMap.Begin(); + it != pni.recordUndoInfoMap.End(); ++it) + { + if ((it->Value().nRecordPos > nDestRecordPos) && (it->Value().nRecordPos < nStartPos)) + { + //This property was set between nDestRecordPos and our start position, so we need to undo it. + nRetVal = UndoRecord(it->Value(), nDestRecordPos, bUndone); + XN_IS_STATUS_OK(nRetVal); + } + } + + if ((i != nNodeID) && pni.bIsGenerator) + { + //Undo all other generator nodes' data + RecordUndoInfo &undoInfo = pni.newDataUndoInfo; + if ((undoInfo.nRecordPos > nDestRecordPos) && (undoInfo.nRecordPos < nStartPos)) + { + nRetVal = UndoRecord(undoInfo, nDestRecordPos, bUndone); + XN_IS_STATUS_OK(nRetVal); + if (!bUndone) + { + //We couldn't find a record that can undo this data record + pni.nLastDataPos = 0; + pni.newDataUndoInfo.Reset(); + } + } + } + } + + /*Now, for each node, go to the position of the last encountered data record, and process that record + (including its payload).*/ + /*TODO: Optimization: remember each node's last data pos, and later, see if it changes. Only process data + frames of nodes whose last data pos actually changed.*/ + + nRetVal = ProcessEachNodeLastData(nNodeID); + XN_IS_STATUS_OK(nRetVal); + } + else //(nDestFrame >= nNextFrame) + { + //Skip all frames until we get to our frame number, but handle any properties we run into. + while (pPlayerNodeInfo->nCurFrame < nDestFrame) + { + nRetVal = ProcessRecord(FALSE); + XN_IS_STATUS_OK(nRetVal); + } + + /*Now, for each node, go to the position of the last encountered data record, and process that record + (including its payload).*/ + /*TODO: Optimization: remember each node's last data pos, and later, see if it changes. Only process data + frames of nodes whose last data pos actually changed.*/ + nRetVal = ProcessEachNodeLastData(nNodeID); + XN_IS_STATUS_OK(nRetVal); + } + } + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::ProcessEachNodeLastData(XnUInt32 nIDToProcessLast) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nItNodeID = 0; //Node ID handled in each iteration. + + for (XnUInt32 i = 0; i < m_nMaxNodes; i++) + { + /*We switch positions between nIDToProcessLast and the last position, to make sure that nIDToProcessLast is + handled last. This way the position at the end of our seek operation is right after the record we read + for nIDToProcessLast.*/ + if (i == nIDToProcessLast) + { + nItNodeID = m_nMaxNodes - 1; + } + else if (i == m_nMaxNodes - 1) + { + nItNodeID = nIDToProcessLast; + } + else + { + nItNodeID = i; + } + PlayerNodeInfo &pni = m_pNodeInfoMap[nItNodeID]; + if (pni.bIsGenerator) + { + if (!pni.bValid) + { + xnLogError(XN_MASK_OPEN_NI, "Node with ID %u is not valid", nItNodeID); + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + if (pni.nLastDataPos == 0) + { + /*This means we had to undo this node's data, but found no data frame before our main node's + data frame. In this case we push a 0 frame.*/ + memset(m_pRecordBuffer, 0, RECORD_MAX_SIZE); + nRetVal = m_pNodeNotifications->OnNodeNewData(m_pNotificationsCookie, pni.strName, 0, 0, m_pRecordBuffer, RECORD_MAX_SIZE); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = SeekStream(XN_OS_SEEK_SET, pni.nLastDataPos); + XN_IS_STATUS_OK(nRetVal); + nRetVal = ProcessRecord(TRUE); + XN_IS_STATUS_OK(nRetVal); + } + } + } + //Now our position is right after the last data of the node with id nIDToProcessLast + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::TellTimestamp(XnUInt64& nTimestamp) +{ + nTimestamp = m_nTimeStamp; + return (XN_STATUS_OK); +} + +XnStatus PlayerNode::TellFrame(const XnChar* strNodeName, XnUInt32& nFrameNumber) +{ + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfoByName(strNodeName); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_BAD_NODE_NAME); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_BAD_NODE_NAME; + } + + nFrameNumber = pPlayerNodeInfo->nCurFrame; + return XN_STATUS_OK; +} + +XnUInt32 PlayerNode::GetNumFrames(const XnChar* strNodeName, XnUInt32& nFrames) +{ + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfoByName(strNodeName); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_BAD_NODE_NAME); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_BAD_NODE_NAME; + } + + nFrames = pPlayerNodeInfo->nFrames; + return XN_STATUS_OK; +} + +const XnChar* PlayerNode::GetSupportedFormat() +{ + return XN_FORMAT_NAME_ONI; +} + +XnBool PlayerNode::IsEOF() +{ + return m_bEOF; +} + +XnStatus PlayerNode::RegisterToEndOfFileReached(EndOfFileReachedHandler handler, void* pCookie, XnCallbackHandle& hCallback) +{ + return m_eofReachedEvent.Register(handler, pCookie, hCallback); +} + +void PlayerNode::UnregisterFromEndOfFileReached(XnCallbackHandle hCallback) +{ + m_eofReachedEvent.Unregister(hCallback); +} + +XnStatus PlayerNode::ValidateStream(void *pStreamCookie, XnPlayerInputStreamInterface* pInputStream) +{ + XN_VALIDATE_INPUT_PTR(pInputStream); + XnStatus nRetVal = pInputStream->Open(pStreamCookie); + XN_IS_STATUS_OK(nRetVal); + RecordingHeader header; + XnUInt32 nBytesRead = 0; + + nRetVal = pInputStream->Read(pStreamCookie, &header, sizeof(header), &nBytesRead); + XN_IS_STATUS_OK(nRetVal); + if (nBytesRead < sizeof(header)) + { + pInputStream->Close(pStreamCookie); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Not enough bytes read"); + } + + /* Check header */ + if (xnOSMemCmp(header.headerMagic, DEFAULT_RECORDING_HEADER.headerMagic, sizeof(header.headerMagic)) != 0) + { + pInputStream->Close(pStreamCookie); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Invalid header magic"); + } + + if ((CompareVersions(&header.version, &OLDEST_SUPPORTED_FILE_FORMAT_VERSION) < 0) || //File format is too old + (CompareVersions(&header.version, &DEFAULT_RECORDING_HEADER.version) > 0)) //File format is too new + { + pInputStream->Close(pStreamCookie); + XN_LOG_ERROR_RETURN(XN_STATUS_UNSUPPORTED_VERSION, XN_MASK_OPEN_NI, "Unsupported file format version: %u.%u.%u.%u", header.version.nMajor, header.version.nMinor, header.version.nMaintenance, header.version.nBuild); + } + + // Close the stream. + pInputStream->Close(pStreamCookie); + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::ReadNext() +{ + return ProcessRecord(TRUE); +} + +XnStatus PlayerNode::ProcessRecord(XnBool bProcessPayload) +{ + //Read a record and handle it + Record record(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + XnStatus nRetVal = ReadRecord(record); + XN_IS_STATUS_OK(nRetVal); + nRetVal = HandleRecord(record, bProcessPayload); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnInt32 PlayerNode::CompareVersions(const XnVersion* pV0, const XnVersion* pV1) +{ + XnInt32 comparison = 0; + comparison = pV0->nMajor - pV1->nMajor; + if (0 != comparison) + { + return comparison; + } + comparison = pV0->nMinor - pV1->nMinor; + if (0 != comparison) + { + return comparison; + } + comparison = pV0->nMaintenance - pV1->nMaintenance; + if (0 != comparison) + { + return comparison; + } + return pV0->nBuild - pV1->nBuild; +} + +XnStatus PlayerNode::OpenStream() +{ + XN_VALIDATE_INPUT_PTR(m_pInputStream); + XnStatus nRetVal = m_pInputStream->Open(m_pStreamCookie); + XN_IS_STATUS_OK(nRetVal); + RecordingHeader header; + XnUInt32 nBytesRead = 0; + + nRetVal = m_pInputStream->Read(m_pStreamCookie, &header, sizeof(header), &nBytesRead); + XN_IS_STATUS_OK(nRetVal); + if (nBytesRead < sizeof(header)) + { + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Not enough bytes read"); + } + + /* Check header */ + if (xnOSMemCmp(header.headerMagic, DEFAULT_RECORDING_HEADER.headerMagic, sizeof(header.headerMagic)) != 0) + { + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Invalid header magic"); + } + + if ((CompareVersions(&header.version, &OLDEST_SUPPORTED_FILE_FORMAT_VERSION) < 0) || //File format is too old + (CompareVersions(&header.version, &DEFAULT_RECORDING_HEADER.version) > 0)) //File format is too new + { + XN_LOG_ERROR_RETURN(XN_STATUS_UNSUPPORTED_VERSION, XN_MASK_OPEN_NI, "Unsupported file format version: %u.%u.%u.%u", header.version.nMajor, header.version.nMinor, header.version.nMaintenance, header.version.nBuild); + } + + /* Do we need to parse an old 32bit-filesize file? */ + if (CompareVersions(&header.version, &FIRST_FILESIZE64BIT_FILE_FORMAT_VERSION) >= 0) + { + m_bIs32bitFileFormat = FALSE; + } else { + m_bIs32bitFileFormat = TRUE; + } + + m_fileVersion = header.version; + m_nGlobalMaxTimeStamp = header.nGlobalMaxTimeStamp; + m_nMaxNodes = header.nMaxNodeID + 1; + XN_ASSERT(m_nMaxNodes > 0); + XN_DELETE_ARR(m_pNodeInfoMap); + xnOSFree(m_aSeekTempArray); + m_pNodeInfoMap = XN_NEW_ARR(PlayerNodeInfo, m_nMaxNodes); + XN_VALIDATE_ALLOC_PTR(m_pNodeInfoMap); + XN_VALIDATE_CALLOC(m_aSeekTempArray, DataIndexEntry*, m_nMaxNodes); + + m_bOpen = TRUE; + nRetVal = ProcessUntilFirstData(); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE_ARR(m_pNodeInfoMap); + m_pNodeInfoMap = NULL; + xnOSFree(m_aSeekTempArray); + m_aSeekTempArray = NULL; + return nRetVal; + } + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::Read(void *pData, XnUInt32 nSize, XnUInt32 &nBytesRead) +{ + XN_VALIDATE_INPUT_PTR(m_pInputStream); + if (!m_bOpen) + { + XN_LOG_ERROR_RETURN(XN_STATUS_INVALID_OPERATION, XN_MASK_OPEN_NI, "Stream was not opened"); + } + + return m_pInputStream->Read(m_pStreamCookie, pData, nSize, &nBytesRead); +} + +XnStatus PlayerNode::ReadRecordHeader(Record &record) +{ + XnUInt32 nBytesRead = 0; + XnStatus nRetVal = Read(record.GetData(), record.HEADER_SIZE, nBytesRead); + XN_IS_STATUS_OK(nRetVal); + + if (nBytesRead != record.HEADER_SIZE) + { + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Incorrect number of bytes read"); + } + + if (!record.IsHeaderValid()) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Invalid record header"); + } + return XN_STATUS_OK; +} + +XnStatus PlayerNode::ReadRecordFields(Record &record) +{ + XnUInt32 nBytesToRead = record.GetSize() - record.HEADER_SIZE; + XnUInt32 nBytesRead = 0; + XnStatus nRetVal = Read(record.GetData() + record.HEADER_SIZE, nBytesToRead, nBytesRead); + XN_IS_STATUS_OK(nRetVal); + if (nBytesRead < nBytesToRead) + { + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Incorrect number of bytes read"); + } + return XN_STATUS_OK; +} + +XnStatus PlayerNode::ReadRecord(Record &record) +{ + XnStatus nRetVal = ReadRecordHeader(record); + XN_IS_STATUS_OK(nRetVal); + nRetVal = ReadRecordFields(record); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SeekStream(XnOSSeekType seekType, XnInt64 nOffset) +{ + XN_VALIDATE_INPUT_PTR(m_pInputStream); + return m_pInputStream->Seek64(m_pStreamCookie, seekType, nOffset); +} + +XnUInt64 PlayerNode::TellStream() +{ + XN_VALIDATE_PTR(m_pInputStream, (XnUInt64)-1); + return m_pInputStream->Tell64(m_pStreamCookie); +} + +XnStatus PlayerNode::CloseStream() +{ + if (m_bOpen) + { + XN_VALIDATE_INPUT_PTR(m_pInputStream); + m_pInputStream->Close(m_pStreamCookie); + m_pInputStream = NULL; + m_pStreamCookie = NULL; + m_bOpen = FALSE; + } + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleRecord(Record &record, XnBool bHandlePayload) +{ + XN_ASSERT(record.IsHeaderValid()); + switch (record.GetType()) + { + case RECORD_NODE_ADDED: + return HandleNodeAddedRecord(record); + case RECORD_INT_PROPERTY: + return HandleIntPropRecord(record); + case RECORD_REAL_PROPERTY: + return HandleRealPropRecord(record); + case RECORD_STRING_PROPERTY: + return HandleStringPropRecord(record); + case RECORD_GENERAL_PROPERTY: + return HandleGeneralPropRecord(record); + case RECORD_NODE_REMOVED: + return HandleNodeRemovedRecord(record); + case RECORD_NODE_STATE_READY: + return HandleNodeStateReadyRecord(record); + case RECORD_NODE_DATA_BEGIN: + return HandleNodeDataBeginRecord(record); + case RECORD_NEW_DATA: + return HandleNewDataRecord(record, bHandlePayload); + case RECORD_SEEK_TABLE: + // never process this record (it is processed only during node added) + return HandleDataIndexRecord(record, FALSE); + case RECORD_END: + return HandleEndRecord(record); + + // BC stuff + case RECORD_NODE_ADDED_1_0_0_5: + return HandleNodeAdded_1_0_0_5_Record(record); + case RECORD_NODE_ADDED_1_0_0_4: + return HandleNodeAdded_1_0_0_4_Record(record); + + default: + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Unrecognized record type: %u", record.GetType()); + } +} + +PlayerNode::PlayerNodeInfo* PlayerNode::GetPlayerNodeInfo(XnUInt32 nNodeID) +{ + if (nNodeID >= m_nMaxNodes) + { + xnLogWarning(XN_MASK_OPEN_NI, "Got node ID %u, bigger than said max of %u", nNodeID, m_nMaxNodes); + XN_ASSERT(FALSE); + return NULL; + } + + return &m_pNodeInfoMap[nNodeID]; +} + +XnStatus PlayerNode::RemovePlayerNodeInfo(XnUInt32 nNodeID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(nNodeID); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (pPlayerNodeInfo->bValid) + { + if (m_pNodeNotifications != NULL) + { + nRetVal = m_pNodeNotifications->OnNodeRemoved(m_pNotificationsCookie, pPlayerNodeInfo->strName); + if (nRetVal != XN_STATUS_OK) + { + return nRetVal; + } + } + + if (pPlayerNodeInfo->pCodec != NULL) + { + m_pNodeCodecFactory->Destroy(m_pNodeCodecFactoryCookie, + pPlayerNodeInfo->pCodec); + pPlayerNodeInfo->pCodec = NULL; + } + pPlayerNodeInfo->Reset(); //Now it's not valid anymore + } + + return XN_STATUS_OK; +} + +XnBool PlayerNode::IsTypeGenerator(XnProductionNodeType type) +{ + if ((type == XN_NODE_TYPE_DEPTH) || + (type == XN_NODE_TYPE_IMAGE) || + (type == XN_NODE_TYPE_IR)) + { + return TRUE; + } + return FALSE; +} + +XnStatus PlayerNode::HandleNodeAddedImpl(XnUInt32 nNodeID, XnProductionNodeType type, const XnChar* strName, XnCodecID compression, XnUInt32 nNumberOfFrames, XnUInt64 /*nMinTimestamp*/, XnUInt64 nMaxTimestamp) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + + XnStatus nRetVal = XN_STATUS_OK; + + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(nNodeID); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + + //Notify node was added + nRetVal = m_pNodeNotifications->OnNodeAdded(m_pNotificationsCookie, strName, type, compression, nNumberOfFrames); + XN_IS_STATUS_OK(nRetVal); + + pPlayerNodeInfo->compression = compression; + nRetVal = xnOSStrCopy(pPlayerNodeInfo->strName, strName, sizeof(pPlayerNodeInfo->strName)); + XN_IS_STATUS_OK(nRetVal); + + if (IsTypeGenerator(type)) + { + pPlayerNodeInfo->bIsGenerator = TRUE; + pPlayerNodeInfo->nFrames = nNumberOfFrames; + pPlayerNodeInfo->nMaxTimeStamp = nMaxTimestamp; + } + + //Mark this player node as valid + pPlayerNodeInfo->bValid = TRUE; + + //Loop until this node's state is ready. + //TODO: Check for eof + while (!pPlayerNodeInfo->bStateReady) + { + nRetVal = ProcessRecord(TRUE); + if (nRetVal != XN_STATUS_OK) + { + pPlayerNodeInfo->bValid = FALSE; + return nRetVal; + } + } + + return (XN_STATUS_OK); +} + +XnStatus PlayerNode::HandleNodeAdded_1_0_0_4_Record(NodeAdded_1_0_0_4_Record record) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + + DEBUG_LOG_RECORD(record, "NodeAdded1_0_0_4"); + + /** BC issue **/ + // NOTE: ONI files up to version 1.0.0.4 didn't had a different NodeAdded record. It did + // not contain seek data (number of frames and min/max timestamp). Instead, this data was + // in the DataBegin record. So we need to also find this record, and read these props from it. + + XnUInt32 nNodeID = record.GetNodeID(); + XnChar strName[XN_MAX_NAME_LENGTH]; + nRetVal = xnOSStrCopy(strName, record.GetNodeName(), XN_MAX_NAME_LENGTH); + XN_IS_STATUS_OK(nRetVal); + XnProductionNodeType type = record.GetNodeType(); + XnCodecID compression = record.GetCompression(); + XnUInt32 nNumFrames = 0; + XnUInt64 nMinTimestamp = 0; + XnUInt64 nMaxTimestamp = 0; + + if (IsTypeGenerator(type)) + { + // we need to look for the DataBegin record to have number of frames, etc. + XnUInt64 nStartPos = TellStream(); + + // NOTE: this overwrites the current NodeAdded record buffer!!! + nRetVal = SeekToRecordByType(nNodeID, RECORD_NODE_DATA_BEGIN); + if (nRetVal == XN_STATUS_OK) + { + NodeDataBeginRecord dataBeginRecord(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + nRetVal = ReadRecord(dataBeginRecord); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = dataBeginRecord.Decode(); + XN_IS_STATUS_OK(nRetVal); + + nNumFrames = dataBeginRecord.GetNumFrames(); + nMaxTimestamp = dataBeginRecord.GetMaxTimeStamp(); + + // also find data record for min timestamp + nRetVal = SeekToRecordByType(record.GetNodeID(), RECORD_NEW_DATA); + if (nRetVal == XN_STATUS_OK) + { + NewDataRecordHeader newDataRecord(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + nRetVal = ReadRecord(newDataRecord); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = newDataRecord.Decode(); + XN_IS_STATUS_OK(nRetVal); + + nMinTimestamp = newDataRecord.GetTimeStamp(); + } + + // get back to start position + nRetVal = SeekStream(XN_OS_SEEK_SET, nStartPos); + XN_IS_STATUS_OK(nRetVal); + } + } + + nRetVal = HandleNodeAddedImpl(nNodeID, type, strName, compression, nNumFrames, nMinTimestamp, nMaxTimestamp); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleNodeAdded_1_0_0_5_Record(NodeAdded_1_0_0_5_Record record) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + + DEBUG_LOG_RECORD(record, "NodeAdded1_0_0_5"); + + nRetVal = HandleNodeAddedImpl( + record.GetNodeID(), record.GetNodeType(), record.GetNodeName(), record.GetCompression(), + record.GetNumberOfFrames(), record.GetMinTimestamp(), record.GetMaxTimestamp()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus PlayerNode::HandleNodeAddedRecord(NodeAddedRecord record) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = record.Decode(); + + XN_IS_STATUS_OK(nRetVal); + + DEBUG_LOG_RECORD(record, "NodeAdded"); + + nRetVal = HandleNodeAddedImpl( + record.GetNodeID(), record.GetNodeType(), record.GetNodeName(), record.GetCompression(), + record.GetNumberOfFrames(), record.GetMinTimestamp(), record.GetMaxTimestamp()); + XN_IS_STATUS_OK(nRetVal); + + // get seek table (if exists) + if (record.GetNumberOfFrames() > 0 && record.GetSeekTablePosition() != 0) + { + XnUInt64 nCurrPos = TellStream(); + + nRetVal = SeekStream(XN_OS_SEEK_SET, record.GetSeekTablePosition()); + XN_IS_STATUS_OK(nRetVal); + + DataIndexRecordHeader seekTableHeader(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + nRetVal = ReadRecord(seekTableHeader); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = HandleDataIndexRecord(seekTableHeader, TRUE); + XN_IS_STATUS_OK(nRetVal); + + // and seek back + nRetVal = SeekStream(XN_OS_SEEK_SET, nCurrPos); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus PlayerNode::SeekToRecordByType(XnUInt32 nNodeID, RecordType type) +{ + XnStatus nRetVal = XN_STATUS_OK; + + Record record(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + + XnUInt64 nStartPos = TellStream(); + + XnBool bFound = FALSE; + XnUInt64 nPosBeforeRecord = 0; + while (!bFound && nRetVal == XN_STATUS_OK) + { + nPosBeforeRecord = TellStream(); + + nRetVal = ReadRecord(record); + XN_IS_STATUS_OK(nRetVal); + + if ((record.GetType() == type) && (record.GetNodeID() == nNodeID)) + { + bFound = TRUE; + } + else if (record.GetType() == RECORD_END) + { + nRetVal = XN_STATUS_NO_MATCH; + } + else + { + // if record has payload, skip it + nRetVal = SkipRecordPayload(record); + } + } + + if (bFound) + { + // seek to before requested record + nRetVal = SeekStream(XN_OS_SEEK_SET, nPosBeforeRecord); + XN_IS_STATUS_OK(nRetVal); + } + else + { + // seek back to starting position + SeekStream(XN_OS_SEEK_SET, nStartPos); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus PlayerNode::HandleGeneralPropRecord(GeneralPropRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "GeneralProp"); + + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + // Save map output mode (resolution) - for real world translation (BC) + if (strcmp(record.GetPropName(), XN_PROP_MAP_OUTPUT_MODE) == 0) + { + xnOSMemCopy(&m_lastOutputMode, record.GetPropData(), sizeof(XnMapOutputMode)); + } + // Fix backwards compatibility issues + if (strcmp(record.GetPropName(), XN_PROP_REAL_WORLD_TRANSLATION_DATA) == 0) + { + // old recordings held the RealWorldTranslationData, but API has changed. Translate + // it to Field Of View + if (record.GetPropDataSize() != sizeof(XnRealWorldTranslationData)) + { + return XN_STATUS_CORRUPT_FILE; + } + + const XnRealWorldTranslationData* pTransData = (const XnRealWorldTranslationData*)record.GetPropData(); + + XnFieldOfView FOV; + + FOV.fHFOV = 2*atan(pTransData->dPixelSizeAtZeroPlane * pTransData->dSourceToDepthPixelRatio * m_lastOutputMode.nXRes / 2 / pTransData->dZeroPlaneDistance); + FOV.fVFOV = 2*atan(pTransData->dPixelSizeAtZeroPlane * pTransData->dSourceToDepthPixelRatio * m_lastOutputMode.nYRes / 2 / pTransData->dZeroPlaneDistance); + + nRetVal = m_pNodeNotifications->OnNodeGeneralPropChanged(m_pNotificationsCookie, + pPlayerNodeInfo->strName, + XN_PROP_FIELD_OF_VIEW, + sizeof(FOV), + &FOV); + XN_IS_STATUS_OK(nRetVal); + + } + else + { + nRetVal = m_pNodeNotifications->OnNodeGeneralPropChanged(m_pNotificationsCookie, + pPlayerNodeInfo->strName, + record.GetPropName(), + record.GetPropDataSize(), + record.GetPropData()); + XN_IS_STATUS_OK(nRetVal); + } + + nRetVal = SaveRecordUndoInfo(pPlayerNodeInfo, + record.GetPropName(), + TellStream() - record.GetSize(), + record.GetUndoRecordPos()); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleIntPropRecord(IntPropRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "IntProp"); + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + const XnChar* strPropName = record.GetPropName(); + XnUInt64 nValue = record.GetValue(); + + // old files workaround: some old files recorded nodes as not generating though having frames. + // make them generating. + if (strcmp(strPropName, XN_PROP_IS_GENERATING) == 0 && + nValue == FALSE && + pPlayerNodeInfo->nFrames > 0) + { + nValue = TRUE; + } + + nRetVal = m_pNodeNotifications->OnNodeIntPropChanged(m_pNotificationsCookie, + pPlayerNodeInfo->strName, + strPropName, + nValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SaveRecordUndoInfo(pPlayerNodeInfo, + record.GetPropName(), + TellStream() - record.GetSize(), + record.GetUndoRecordPos()); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleRealPropRecord(RealPropRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "RealProp"); + + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + nRetVal = m_pNodeNotifications->OnNodeRealPropChanged(m_pNotificationsCookie, + pPlayerNodeInfo->strName, + record.GetPropName(), + record.GetValue()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SaveRecordUndoInfo(pPlayerNodeInfo, + record.GetPropName(), + TellStream() - record.GetSize(), + record.GetUndoRecordPos()); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleStringPropRecord(StringPropRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "StringProp"); + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + nRetVal = m_pNodeNotifications->OnNodeStringPropChanged(m_pNotificationsCookie, + pPlayerNodeInfo->strName, + record.GetPropName(), + record.GetValue()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SaveRecordUndoInfo(pPlayerNodeInfo, + record.GetPropName(), + TellStream() - record.GetSize(), + record.GetUndoRecordPos()); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleNodeRemovedRecord(NodeRemovedRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "NodeRemoved"); + + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Got a node removed record for non-existing node %u.", record.GetNodeID()); + } + + nRetVal = RemovePlayerNodeInfo(record.GetNodeID()); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleNodeStateReadyRecord(NodeStateReadyRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "NodeStateReady"); + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + // after wrap-around, if node wasn't destroyed, no need to notify about state ready + if (!pPlayerNodeInfo->bStateReady) + { + nRetVal = m_pNodeNotifications->OnNodeStateReady(m_pNotificationsCookie, pPlayerNodeInfo->strName); + XN_IS_STATUS_OK(nRetVal); + } + + if (pPlayerNodeInfo->bIsGenerator && + (pPlayerNodeInfo->compression != XN_CODEC_NULL) && + (pPlayerNodeInfo->pCodec == NULL)) + { + // TODO: create codec + if (m_pNodeCodecFactory == NULL) + { + XN_ASSERT(FALSE); + return XN_STATUS_NOT_INIT; + } + + // Create the required codec. + nRetVal = m_pNodeCodecFactory->Create(m_pNodeCodecFactoryCookie, + pPlayerNodeInfo->strName, + pPlayerNodeInfo->compression, + &pPlayerNodeInfo->pCodec); + XN_IS_STATUS_OK_LOG_ERROR("Create codec", nRetVal); + } + + pPlayerNodeInfo->bStateReady = TRUE; + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleNodeDataBeginRecord(NodeDataBeginRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "NodeDataBegin"); + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + if (!pPlayerNodeInfo->bIsGenerator) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Got data for non-generator node '%s'", pPlayerNodeInfo->strName); + } + + m_bDataBegun = TRUE; + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleNewDataRecord(NewDataRecordHeader record, XnBool bReadPayload) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "NewData"); + + XN_ASSERT(record.GetNodeID() != INVALID_NODE_ID); + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + XnUInt32 nRecordTotalSize = record.GetSize() + record.GetPayloadSize(); + if (nRecordTotalSize > RECORD_MAX_SIZE) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_INTERNAL_BUFFER_TOO_SMALL, XN_MASK_OPEN_NI, "Record size %u is larger than player internal buffer", nRecordTotalSize); + } + + pPlayerNodeInfo->nLastDataPos = TellStream() - record.GetSize(); + pPlayerNodeInfo->newDataUndoInfo.nRecordPos = pPlayerNodeInfo->nLastDataPos; + pPlayerNodeInfo->newDataUndoInfo.nUndoRecordPos = record.GetUndoRecordPos(); + if (record.GetFrameNumber() > pPlayerNodeInfo->nFrames) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + pPlayerNodeInfo->nCurFrame = record.GetFrameNumber(); + + if (record.GetTimeStamp() > m_nGlobalMaxTimeStamp) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Record timestamp for record in position %u is larger than reported max timestamp", pPlayerNodeInfo->nLastDataPos); + } + + m_nTimeStamp = record.GetTimeStamp(); + + if (bReadPayload) + { + //Now read the actual data + XnUInt32 nBytesRead = 0; + nRetVal = Read(record.GetPayload(), record.GetPayloadSize(), nBytesRead); + XN_IS_STATUS_OK(nRetVal); + if (nBytesRead < record.GetPayloadSize()) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Not enough bytes read"); + } + + const XnUInt8* pCompressedData = record.GetPayload(); //The new (compressed) data is right at the end of the header + XnUInt32 nCompressedDataSize = record.GetPayloadSize(); + const XnUInt8* pUncompressedData = NULL; + XnUInt32 nUncompressedDataSize = 0; + XnCodecID compression = (pPlayerNodeInfo->pCodec == NULL) ? XN_CODEC_NULL : + pPlayerNodeInfo->pCodec->GetCodecID(); + if (compression == XN_CODEC_UNCOMPRESSED) + { + pUncompressedData = pCompressedData; + nUncompressedDataSize = nCompressedDataSize; + } + else + { + //Decode data with codec + nUncompressedDataSize = DATA_MAX_SIZE; + nRetVal = pPlayerNodeInfo->pCodec->Decompress(pCompressedData, nCompressedDataSize, + m_pUncompressedData, &nUncompressedDataSize); + XN_IS_STATUS_OK_ASSERT(nRetVal); + pUncompressedData = m_pUncompressedData; + } + + nRetVal = m_pNodeNotifications->OnNodeNewData(m_pNotificationsCookie, pPlayerNodeInfo->strName, + record.GetTimeStamp(), record.GetFrameNumber(), + pUncompressedData, nUncompressedDataSize); + XN_IS_STATUS_OK_ASSERT(nRetVal); + } + else + { + //Just skip the data + nRetVal = SkipRecordPayload(record); + XN_IS_STATUS_OK_ASSERT(nRetVal); + } + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleDataIndexRecord(DataIndexRecordHeader record, XnBool bReadPayload) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + nRetVal = record.Decode(); + XN_IS_STATUS_OK_ASSERT(nRetVal); + DEBUG_LOG_RECORD(record, "DataIndex"); + + XN_ASSERT(record.GetNodeID() != INVALID_NODE_ID); + PlayerNodeInfo* pPlayerNodeInfo = GetPlayerNodeInfo(record.GetNodeID()); + XN_VALIDATE_PTR(pPlayerNodeInfo, XN_STATUS_CORRUPT_FILE); + + XnUInt32 nRecordTotalSize = record.GetSize() + record.GetPayloadSize(); + if (nRecordTotalSize > RECORD_MAX_SIZE) + { + XN_ASSERT(FALSE); + XN_LOG_ERROR_RETURN(XN_STATUS_INTERNAL_BUFFER_TOO_SMALL, XN_MASK_OPEN_NI, "Record size %u is larger than player internal buffer", nRecordTotalSize); + } + + if (bReadPayload) + { + // make sure node exists + if (!pPlayerNodeInfo->bValid) + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + XnUInt32 DIESize; + if (m_bIs32bitFileFormat) DIESize = sizeof(DataIndexEntry_old32); + else DIESize = sizeof(DataIndexEntry); + + if (record.GetPayloadSize() != (pPlayerNodeInfo->nFrames+1) * DIESize) + { + XN_ASSERT(FALSE); + XN_LOG_WARNING_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Seek table has %u entries, but node has %u frames!", record.GetPayloadSize() / DIESize, pPlayerNodeInfo->nFrames); + } + + // allocate our data index + pPlayerNodeInfo->pDataIndex = (DataIndexEntry*)xnOSCalloc(pPlayerNodeInfo->nFrames+1, sizeof(DataIndexEntry)); + XN_VALIDATE_ALLOC_PTR(pPlayerNodeInfo->pDataIndex); + + //Now read the actual data + XnUInt32 nBytesRead = 0; + + if (m_bIs32bitFileFormat) + { + DataIndexEntry_old32 old32; + XnUInt32 nRead = 0; + for(XnUInt32 n = 0; n < pPlayerNodeInfo->nFrames+1; n++) + { + nRetVal = Read(&old32, sizeof(DataIndexEntry_old32), nRead); + XN_IS_STATUS_OK(nRetVal); nBytesRead += nRead; + DataIndexEntry::FillFromOld32Entry(&pPlayerNodeInfo->pDataIndex[n], &old32); + } + } + else + { + nRetVal = Read(pPlayerNodeInfo->pDataIndex, record.GetPayloadSize(), nBytesRead); + XN_IS_STATUS_OK(nRetVal); + } + + if (nBytesRead < record.GetPayloadSize()) + { + XN_LOG_ERROR_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "Not enough bytes read"); + } + } + else + { + //Just skip the data + nRetVal = SkipRecordPayload(record); + XN_IS_STATUS_OK(nRetVal); + } + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::HandleEndRecord(EndRecord record) +{ + XN_VALIDATE_INPUT_PTR(m_pNodeNotifications); + XnStatus nRetVal = record.Decode(); + XN_IS_STATUS_OK(nRetVal); + DEBUG_LOG_RECORD(record, "End"); + + if (!m_bDataBegun) + { + XN_LOG_WARNING_RETURN(XN_STATUS_CORRUPT_FILE, XN_MASK_OPEN_NI, "File does not contain any data!"); + } + + m_bEOF = !m_bRepeat; + + nRetVal = m_eofReachedEvent.Raise(); + XN_IS_STATUS_OK(nRetVal); + + if (m_bRepeat) + { + nRetVal = Rewind(); + XN_IS_STATUS_OK(nRetVal); + } + else + { + CloseStream(); + } + + return XN_STATUS_OK; +} + + +XnStatus PlayerNode::Rewind() +{ + //skip recording header + XnStatus nRetVal = SeekStream(XN_OS_SEEK_SET, sizeof(RecordingHeader)); + XN_IS_STATUS_OK(nRetVal); + + //Reset all node info's + for (XnUInt32 i = 0; i < m_nMaxNodes; i++) + { + m_pNodeInfoMap[i].Reset(); + } + + m_bDataBegun = FALSE; + m_nTimeStamp = 0; + m_bEOF = FALSE; + + //Skip to first data + nRetVal = ProcessUntilFirstData(); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus PlayerNode::ProcessUntilFirstData() +{ + XnStatus nRetVal = XN_STATUS_OK; + //Loop until we get to the first 'data begin' marker in the file. + + while (!m_bDataBegun) + { + nRetVal = ProcessRecord(TRUE); + XN_IS_STATUS_OK(nRetVal); + } + + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SeekToTimeStampAbsolute(XnUInt64 nDestTimeStamp) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt64 nRecordTimeStamp = 0LL; + XnUInt64 nStartPos = TellStream(); //We'll revert to this in case nDestTimeStamp is beyond end of stream + XN_IS_STATUS_OK(nRetVal); + + if (nDestTimeStamp < m_nTimeStamp) + { + nRetVal = Rewind(); + } + else if (nDestTimeStamp == m_nTimeStamp) + { + //Nothing to do + return XN_STATUS_OK; + } + else if (nDestTimeStamp > m_nGlobalMaxTimeStamp) + { + nDestTimeStamp = m_nGlobalMaxTimeStamp; + } + + Record record(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + XnBool bEnd = FALSE; + XnUInt32 nBytesRead = 0; + + while ((nRecordTimeStamp < nDestTimeStamp) && !bEnd) + { + nRetVal = ReadRecordHeader(record); + XN_IS_STATUS_OK(nRetVal); + switch (record.GetType()) + { + case RECORD_NEW_DATA: + { + //We already read Record::HEADER_SIZE, now read the rest of the new data record header + nRetVal = Read(m_pRecordBuffer + record.HEADER_SIZE, + NewDataRecordHeader::MAX_SIZE - record.HEADER_SIZE, + nBytesRead); + XN_IS_STATUS_OK(nRetVal); + if (nBytesRead < NewDataRecordHeader::MAX_SIZE - record.HEADER_SIZE) + { + return XN_STATUS_CORRUPT_FILE; + } + NewDataRecordHeader newDataRecordHeader(record); + nRetVal = newDataRecordHeader.Decode(); + XN_IS_STATUS_OK(nRetVal); + //Save record time stamp + nRecordTimeStamp = newDataRecordHeader.GetTimeStamp(); + + if (nRecordTimeStamp >= nDestTimeStamp) + { + //We're done - move back to beginning of record + nRetVal = SeekStream(XN_OS_SEEK_CUR, -XnInt32(nBytesRead)); + XN_IS_STATUS_OK(nRetVal); + } + else + { + //Skip to next record + nRetVal = SeekStream(XN_OS_SEEK_CUR, + newDataRecordHeader.GetSize() - NewDataRecordHeader::MAX_SIZE); + XN_IS_STATUS_OK(nRetVal); + } + break; + } + + case RECORD_END: + { + bEnd = TRUE; + break; + } + + case RECORD_NODE_ADDED_1_0_0_4: + case RECORD_NODE_ADDED_1_0_0_5: + case RECORD_NODE_ADDED: + case RECORD_INT_PROPERTY: + case RECORD_REAL_PROPERTY: + case RECORD_STRING_PROPERTY: + case RECORD_GENERAL_PROPERTY: + case RECORD_NODE_REMOVED: + case RECORD_NODE_DATA_BEGIN: + case RECORD_NODE_STATE_READY: + { + //Read rest of record and handle it normally + nRetVal = Read(m_pRecordBuffer + record.HEADER_SIZE, record.GetSize() - record.HEADER_SIZE, nBytesRead); + XN_IS_STATUS_OK(nRetVal); + Record record(m_pRecordBuffer, RECORD_MAX_SIZE, m_bIs32bitFileFormat); + nRetVal = HandleRecord(record, TRUE); + XN_IS_STATUS_OK(nRetVal); + break; + } + default: + { + XN_ASSERT(FALSE); + return XN_STATUS_CORRUPT_FILE; + } + + } //switch + } //while + + if (bEnd) + { + SeekStream(XN_OS_SEEK_SET, nStartPos); + return XN_STATUS_ILLEGAL_POSITION; + } + + return XN_STATUS_OK; +}//function + +XnStatus PlayerNode::SeekToTimeStampRelative(XnInt64 nOffset) +{ + //TODO: Implement more efficiently + return SeekToTimeStampAbsolute(m_nTimeStamp + nOffset); +} + +XnUInt32 PlayerNode::GetPlayerNodeIDByName(const XnChar* strNodeName) +{ + for (XnUInt32 i = 0; i < m_nMaxNodes; i++) + { + if (xnOSStrCmp(strNodeName, m_pNodeInfoMap[i].strName) == 0) + { + return i; + } + } + return INVALID_NODE_ID; +} + +PlayerNode::PlayerNodeInfo* PlayerNode::GetPlayerNodeInfoByName(const XnChar* strNodeName) +{ + XnUInt32 nNodeID = GetPlayerNodeIDByName(strNodeName); + return (nNodeID == INVALID_NODE_ID) ? NULL : &m_pNodeInfoMap[nNodeID]; +} + +XnStatus PlayerNode::SaveRecordUndoInfo(PlayerNodeInfo* pPlayerNodeInfo, + const XnChar* strPropName, + XnUInt64 nRecordPos, + XnUInt64 nUndoRecordPos) +{ + RecordUndoInfo recordUndoInfo; + recordUndoInfo.nRecordPos = nRecordPos; + recordUndoInfo.nUndoRecordPos = nUndoRecordPos; + XnStatus nRetVal = pPlayerNodeInfo->recordUndoInfoMap.Set(strPropName, recordUndoInfo); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +XnStatus PlayerNode::GetRecordUndoInfo(PlayerNodeInfo* pPlayerNodeInfo, const XnChar* strPropName, XnUInt64& nRecordPos, XnUInt64& nUndoRecordPos) +{ + RecordUndoInfo *pRecordUndoInfo = NULL; + XnStatus nRetVal = pPlayerNodeInfo->recordUndoInfoMap.Get(strPropName, pRecordUndoInfo); + XN_IS_STATUS_OK(nRetVal); + nRecordPos = pRecordUndoInfo->nRecordPos; + nUndoRecordPos = pRecordUndoInfo->nUndoRecordPos; + return XN_STATUS_OK; +} + +XnStatus PlayerNode::SkipRecordPayload(Record record) +{ + return SeekStream(XN_OS_SEEK_CUR, record.GetPayloadSize()); +} + +PlayerNode::PlayerNodeInfo::PlayerNodeInfo() +{ + pCodec = NULL; + pDataIndex = NULL; + Reset(); +} + +PlayerNode::PlayerNodeInfo::~PlayerNodeInfo() +{ +} + +void PlayerNode::PlayerNodeInfo::Reset() +{ + xnOSMemSet(strName, 0, sizeof(strName)); + nLastDataPos = 0; + compression = XN_CODEC_NULL; + nFrames = 0; + nCurFrame = 0; + nMaxTimeStamp = 0; + bStateReady = FALSE; + bIsGenerator = FALSE; + recordUndoInfoMap.Clear(); + newDataUndoInfo.Reset(); + bValid = FALSE; + xnOSFree(pDataIndex); + pDataIndex = NULL; +} + +} diff --git a/Source/Drivers/OniFile/PlayerNode.h b/Source/Drivers/OniFile/PlayerNode.h new file mode 100644 index 0000000..3c215a3 --- /dev/null +++ b/Source/Drivers/OniFile/PlayerNode.h @@ -0,0 +1,195 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __PLAYER_H__ +#define __PLAYER_H__ + +#include "DataRecords.h" +#include "XnPlayerTypes.h" +#include "Formats/XnCodecIDs.h" +#include "Formats/XnStreamFormats.h" +#include "XnHash.h" +#include "XnEvent.h" + +class XnCodec; + +namespace oni_file { + +class PlayerNode +{ +public: + + /** Prototype for state change callback function. **/ + typedef void (XN_CALLBACK_TYPE* EndOfFileReachedHandler)(void* pCookie); + + typedef struct + { + XnStatus (XN_CALLBACK_TYPE* Create)(void* pCookie, const char* strNodeName, XnCodecID nCodecId, XnCodec** ppCodec); + void (XN_CALLBACK_TYPE* Destroy)(void* pCookie, XnCodec* pCodec); + } CodecFactory; + +public: + PlayerNode(const XnChar* strName); + virtual ~PlayerNode(); + + //public functions + virtual XnStatus Init(); + virtual XnStatus Destroy(); + + //xn::ModulePlayer implementation + virtual XnStatus SetInputStream(void* pStreamCookie, XnPlayerInputStreamInterface* pStream); + virtual XnStatus ReadNext(); + virtual XnStatus SetNodeNotifications(void* pNotificationsCookie, XnNodeNotifications* pNodeNotifications); + virtual XnStatus SetNodeCodecFactory(void* pFactoryCookie, PlayerNode::CodecFactory* pPlayerNodeCodecFactory); + virtual XnStatus SetRepeat(XnBool bRepeat); + virtual XnStatus SeekToTimeStamp(XnInt64 nTimeOffset, XnPlayerSeekOrigin origin); + + virtual XnStatus SeekToFrame(const XnChar* strNodeName, XnInt32 nFrameOffset, XnPlayerSeekOrigin origin); + virtual XnStatus TellTimestamp(XnUInt64& nTimestamp); + virtual XnStatus TellFrame(const XnChar* strNodeName, XnUInt32& nFrameNumber); + virtual XnUInt32 GetNumFrames(const XnChar* strNodeName, XnUInt32& nFrames); + + virtual const XnChar* GetSupportedFormat(); + virtual XnBool IsEOF(); + virtual XnStatus RegisterToEndOfFileReached(EndOfFileReachedHandler handler, void* pCookie, XnCallbackHandle& hCallback); + virtual void UnregisterFromEndOfFileReached(XnCallbackHandle hCallback); + + static XnStatus ValidateStream(void *pStreamCookie, XnPlayerInputStreamInterface* pInputStream); + +private: + struct RecordUndoInfo + { + RecordUndoInfo() { Reset(); } + void Reset() { nRecordPos = 0; nUndoRecordPos = 0; } + XnUInt64 nRecordPos; + XnUInt64 nUndoRecordPos; + }; + + typedef xnl::StringsHash RecordUndoInfoMap; + + struct PlayerNodeInfo + { + PlayerNodeInfo(); + ~PlayerNodeInfo(); + + void Reset(); + + XnBool bValid; + XnChar strName[XN_MAX_NAME_LENGTH]; + XnUInt64 nLastDataPos; + XnCodecID compression; + XnUInt32 nFrames; + XnUInt32 nCurFrame; + XnUInt64 nMaxTimeStamp; + XnBool bStateReady; + XnBool bIsGenerator; + XnCodec* pCodec; + RecordUndoInfoMap recordUndoInfoMap; + RecordUndoInfo newDataUndoInfo; + DataIndexEntry* pDataIndex; + }; + + XnStatus ProcessRecord(XnBool bProcessPayload); + XnStatus SeekToTimeStampAbsolute(XnUInt64 nDestTimeStamp); + XnStatus SeekToTimeStampRelative(XnInt64 nOffset); + XnStatus UndoRecord(PlayerNode::RecordUndoInfo& undoInfo, XnUInt64 nDestPos, XnBool& nUndone); + XnStatus SeekToFrameAbsolute(XnUInt32 nNodeID, XnUInt32 nFrameNumber); + XnStatus ProcessEachNodeLastData(XnUInt32 nIDToProcessLast); + + static XnInt32 CompareVersions(const XnVersion* pV0, const XnVersion* pV1); + XnStatus OpenStream(); + XnStatus Read(void* pData, XnUInt32 nSize, XnUInt32& nBytesRead); + XnStatus ReadRecordHeader(Record& record); + XnStatus ReadRecordFields(Record& record); + //ReadRecord reads just the fields of the record, not the payload. + XnStatus ReadRecord(Record& record); + XnStatus SeekStream(XnOSSeekType seekType, XnInt64 nOffset); + XnUInt64 TellStream(); + XnStatus CloseStream(); + + XnBool IsTypeGenerator(XnProductionNodeType type); + + XnStatus HandleRecord(Record& record, XnBool bHandleRecord); + XnStatus HandleNodeAddedImpl(XnUInt32 nNodeID, XnProductionNodeType type, const XnChar* strName, XnCodecID compression, XnUInt32 nNumberOfFrames, XnUInt64 nMinTimestamp, XnUInt64 nMaxTimestamp); + XnStatus HandleNodeAddedRecord(NodeAddedRecord record); + XnStatus HandleGeneralPropRecord(GeneralPropRecord record); + XnStatus HandleIntPropRecord(IntPropRecord record); + XnStatus HandleRealPropRecord(RealPropRecord record); + XnStatus HandleStringPropRecord(StringPropRecord record); + XnStatus HandleNodeRemovedRecord(NodeRemovedRecord record); + XnStatus HandleNodeStateReadyRecord(NodeStateReadyRecord record); + XnStatus HandleNodeDataBeginRecord(NodeDataBeginRecord record); + XnStatus HandleNewDataRecord(NewDataRecordHeader record, XnBool bHandleRecord); + XnStatus HandleDataIndexRecord(DataIndexRecordHeader record, XnBool bReadPayload); + XnStatus HandleEndRecord(EndRecord record); + XnStatus Rewind(); + XnStatus ProcessUntilFirstData(); + PlayerNodeInfo* GetPlayerNodeInfo(XnUInt32 nNodeID); + XnStatus RemovePlayerNodeInfo(XnUInt32 nNodeID); + XnUInt32 GetPlayerNodeIDByName(const XnChar* strNodeName); + PlayerNodeInfo* GetPlayerNodeInfoByName(const XnChar* strNodeName); + XnStatus SaveRecordUndoInfo(PlayerNodeInfo* pPlayerNodeInfo, const XnChar* strPropName, XnUInt64 nRecordPos, XnUInt64 nUndoRecordPos); + XnStatus GetRecordUndoInfo(PlayerNodeInfo* pPlayerNodeInfo, const XnChar* strPropName, XnUInt64& nRecordPos, XnUInt64& nUndoRecordPos); + XnStatus SkipRecordPayload(Record record); + XnStatus SeekToRecordByType(XnUInt32 nNodeID, RecordType type); + DataIndexEntry* FindTimestampInDataIndex(XnUInt32 nNodeID, XnUInt64 nTimestamp); + DataIndexEntry** GetSeekLocationsFromDataIndex(XnUInt32 nNodeID, XnUInt32 nDestFrame); + + // BC functions + XnStatus HandleNodeAdded_1_0_0_5_Record(NodeAdded_1_0_0_5_Record record); + XnStatus HandleNodeAdded_1_0_0_4_Record(NodeAdded_1_0_0_4_Record record); + + static const XnUInt64 DATA_MAX_SIZE; + static const XnUInt64 RECORD_MAX_SIZE; + static const XnVersion OLDEST_SUPPORTED_FILE_FORMAT_VERSION; + static const XnVersion FIRST_FILESIZE64BIT_FILE_FORMAT_VERSION; + + XnVersion m_fileVersion; + XnChar m_strName[XN_MAX_NAME_LENGTH]; + XnBool m_bOpen; + XnBool m_bIs32bitFileFormat; + XnUInt8* m_pRecordBuffer; + XnUInt8* m_pUncompressedData; + void* m_pStreamCookie; + XnPlayerInputStreamInterface* m_pInputStream; + void* m_pNotificationsCookie; + XnNodeNotifications* m_pNodeNotifications; + void* m_pNodeCodecFactoryCookie; + PlayerNode::CodecFactory* m_pNodeCodecFactory; + XnBool m_bRepeat; + XnBool m_bDataBegun; + XnBool m_bEOF; + + XnUInt64 m_nTimeStamp; + XnUInt64 m_nGlobalMaxTimeStamp; + + xnl::EventNoArgs m_eofReachedEvent; + + PlayerNodeInfo* m_pNodeInfoMap; + XnUInt32 m_nMaxNodes; + + DataIndexEntry** m_aSeekTempArray; + + XnMapOutputMode m_lastOutputMode; +}; + +} + +#endif //__PLAYER_H__ diff --git a/Source/Drivers/OniFile/PlayerProperties.h b/Source/Drivers/OniFile/PlayerProperties.h new file mode 100644 index 0000000..64ce6b9 --- /dev/null +++ b/Source/Drivers/OniFile/PlayerProperties.h @@ -0,0 +1,122 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __PLAYER_PROPERTIES_H__ +#define __PLAYER_PROPERTIES_H__ + +#include "XnHash.h" +#include "Driver/OniDriverTypes.h" + +namespace oni_file { + +class PlayerProperties +{ +public: + + ~PlayerProperties() + { + while (m_properties.Begin() != m_properties.End()) + { + PropertiesHash::Iterator iter = m_properties.Begin(); + OniGeneralBuffer* pBuffer = (*iter).second; + DestroyBuffer(pBuffer); + m_properties.Remove(iter); + } + } + + OniBool Exists(int propertyId) + { + if (m_properties.Find(propertyId) != m_properties.End()) + { + return TRUE; + } + return FALSE; + } + + OniStatus GetProperty(int propertyId, void* data, int* pDataSize) const + { + // Get the property and make sure it exists. + PropertiesHash::ConstIterator iter = m_properties.Find(propertyId); + if (iter == m_properties.End()) + { + return ONI_STATUS_ERROR; + } + + // Copy the property. + const OniGeneralBuffer* pBuffer = (*iter).second; + *pDataSize = (*pDataSize < pBuffer->dataSize) ? *pDataSize : pBuffer->dataSize; + memcpy(data, pBuffer->data, *pDataSize); + + return ONI_STATUS_OK; + } + + OniStatus SetProperty(int propertyId, const void* data, int dataSize) + { + // Check if property exists. + PropertiesHash::Iterator iter = m_properties.Find(propertyId); + if (iter != m_properties.End()) + { + DestroyBuffer((*iter).second); + m_properties.Remove(iter); + } + + // Copy the property. + OniGeneralBuffer* pBuffer = CreateBuffer(data, dataSize); + m_properties[propertyId] = pBuffer; + + return ONI_STATUS_OK; + } + +private: + + OniGeneralBuffer* CreateBuffer(const void* data, int dataSize) + { + OniGeneralBuffer* pBuffer; + + pBuffer = XN_NEW(OniGeneralBuffer); + if (pBuffer == NULL) + { + return NULL; + } + pBuffer->data = XN_NEW_ARR(unsigned char, dataSize); + if (pBuffer->data == NULL) + { + XN_DELETE(pBuffer); + return NULL; + } + memcpy(pBuffer->data, data, dataSize); + pBuffer->dataSize = dataSize; + + return pBuffer; + } + + void DestroyBuffer(OniGeneralBuffer* pBuffer) + { + XN_DELETE_ARR((unsigned char*)pBuffer->data); + XN_DELETE(pBuffer); + } + + typedef xnl::Hash PropertiesHash; + PropertiesHash m_properties; +}; + +} // namespace oni_files_player + +#endif //__PLAYER_PROPERTIES_H__ diff --git a/Source/Drivers/OniFile/PlayerSource.cpp b/Source/Drivers/OniFile/PlayerSource.cpp new file mode 100644 index 0000000..747cc7a --- /dev/null +++ b/Source/Drivers/OniFile/PlayerSource.cpp @@ -0,0 +1,159 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the definition of Device class that implements a virtual OpenNI +/// device, capable of reading data from a *.ONI file. + +#include "PlayerSource.h" + +namespace oni_file { + +/// Constructor. +PlayerSource::PlayerSource(const XnChar* strNodeName, OniSensorType sensorType) : + m_nodeName(strNodeName), + m_requiredFrameSize(0) +{ + m_sourceInfo.sensorType = sensorType; + m_sourceInfo.numSupportedVideoModes = 0; +} + + +/// Destructor. +PlayerSource::~PlayerSource() +{ +} + +/// Return the source info associated with the source. +OniSensorInfo* PlayerSource::GetInfo() +{ + return &m_sourceInfo; +} + +const XnChar* PlayerSource::GetNodeName() +{ + return m_nodeName.Data(); +} + +/// Get property. +OniStatus PlayerSource::GetProperty(int propertyId, void* data, int* pDataSize) +{ + return m_properties.GetProperty(propertyId, data, pDataSize); +} + +/// Set property. +OniStatus PlayerSource::SetProperty(int propertyId, const void* data, int dataSize) +{ + if (propertyId == ONI_STREAM_PROPERTY_VIDEO_MODE) + { + if(m_sourceInfo.numSupportedVideoModes == 0) + { + // at first, we're gonna allocate and set. + // next time we'll just update the values. + m_sourceInfo.numSupportedVideoModes = 1; + m_sourceInfo.pSupportedVideoModes = XN_NEW(OniVideoMode); + } + memcpy(m_sourceInfo.pSupportedVideoModes, data, sizeof(OniVideoMode)); + + // Calculate bytes-per-pixel from format and number of maps per frame. + OniVideoMode* pVideoMode = (OniVideoMode*)data; + int stride; + int bytesPerPixel; + switch (pVideoMode->pixelFormat) + { + case ONI_PIXEL_FORMAT_GRAY8: + { + bytesPerPixel = 1; + stride = pVideoMode->resolutionX * bytesPerPixel; + break; + } + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + case ONI_PIXEL_FORMAT_DEPTH_100_UM: + case ONI_PIXEL_FORMAT_SHIFT_9_2: + case ONI_PIXEL_FORMAT_SHIFT_9_3: + { + bytesPerPixel = 2; + stride = pVideoMode->resolutionX * bytesPerPixel; + break; + } + case ONI_PIXEL_FORMAT_GRAY16: + case ONI_PIXEL_FORMAT_YUV422: + case ONI_PIXEL_FORMAT_YUYV: + { + bytesPerPixel = 2; + stride = pVideoMode->resolutionX * bytesPerPixel; + break; + } + case ONI_PIXEL_FORMAT_RGB888: + { + bytesPerPixel = 3; + stride = pVideoMode->resolutionX * bytesPerPixel; + break; + } + case ONI_PIXEL_FORMAT_JPEG: + { + bytesPerPixel = 0; + stride = 0; + break; + } + default: + { + bytesPerPixel = 0; + stride = 0; + } + } + + m_properties.SetProperty(ONI_STREAM_PROPERTY_BYTES_PER_PIXEL, &bytesPerPixel, sizeof(bytesPerPixel)); + m_properties.SetProperty(ONI_STREAM_PROPERTY_STRIDE, &stride, sizeof(stride)); + } + + return m_properties.SetProperty(propertyId, data, dataSize); +} + +// Process new data. +void PlayerSource::ProcessNewData(XnUInt64 nTimeStamp, XnUInt32 nFrameId, void* pData, XnUInt32 nSize) +{ + // Raise the event to all registered callbacks. + NewDataEventArgs args; + args.nTimeStamp = nTimeStamp; + args.nFrameId = nFrameId; + args.pData = pData; + args.nSize = nSize; + m_newDataEvent.Raise(args); +} + +// Register for new data event. +OniStatus PlayerSource::RegisterNewDataEvent(NewDataCallback callback, void* pCookie, OniCallbackHandle& handle) +{ + XnStatus rc = m_newDataEvent.Register(callback, pCookie, (XnCallbackHandle&)handle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + return ONI_STATUS_OK; +} + +// Unregister from new data event. +void PlayerSource::UnregisterNewDataEvent(OniCallbackHandle handle) +{ + m_newDataEvent.Unregister((XnCallbackHandle)handle); +} + +} // namespace oni_files_player diff --git a/Source/Drivers/OniFile/PlayerSource.h b/Source/Drivers/OniFile/PlayerSource.h new file mode 100644 index 0000000..631a079 --- /dev/null +++ b/Source/Drivers/OniFile/PlayerSource.h @@ -0,0 +1,109 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the declaration of Device class that implements a virtual OpenNI +/// device, capable of reading data from a *.ONI file. + +#ifndef __PLAYER_SOURCE_H__ +#define __PLAYER_SOURCE_H__ + +#include "PlayerProperties.h" +#include "OniCProperties.h" +#include "XnEvent.h" +#include "XnString.h" + +enum +{ + ONI_STREAM_PROPERTY_BYTES_PER_PIXEL = ONI_STREAM_PROPERTY_PRIVATE_BASE, //int +}; + +namespace oni_file { + +class Codec; + +/// Represents a source in a played .ONI file. +class PlayerSource +{ +public: + // New data event. + typedef struct + { + XnUInt64 nTimeStamp; + XnUInt32 nFrameId; + void* pData; + XnUInt32 nSize; + } NewDataEventArgs; + typedef xnl::Event NewDataEvent; + typedef void (ONI_CALLBACK_TYPE* NewDataCallback)(const NewDataEventArgs& newDataEventArgs, void* pCookie); + +public: + + /// Constructor. + PlayerSource(const XnChar* strNodeName, OniSensorType sensorType); + + /// Destructor. + virtual ~PlayerSource(); + + /// Return the source info associated with the source. + OniSensorInfo* GetInfo(); + + /// Return the node name for the source. + const XnChar* GetNodeName(); + + /// Get property. + virtual OniStatus GetProperty(int propertyId, void* data, int* pDataSize); + + /// Set property. + virtual OniStatus SetProperty(int propertyId, const void* data, int dataSize); + + // Process new data. + void ProcessNewData(XnUInt64 nTimeStamp, XnUInt32 nFrameId, void* pData, XnUInt32 nSize); + + // Register for new data event. + OniStatus RegisterNewDataEvent(NewDataCallback callback, void* pCookie, OniCallbackHandle& handle); + + // Unregister from new data event. + void UnregisterNewDataEvent(OniCallbackHandle handle); + + void SetRequiredFrameSize(int requiredFrameSize) { m_requiredFrameSize = requiredFrameSize; } + int GetRequiredFrameSize() const { return m_requiredFrameSize; } + +protected: + XN_DISABLE_COPY_AND_ASSIGN(PlayerSource); + + // Name of the node. + const xnl::String m_nodeName; + + // Source information. + OniSensorInfo m_sourceInfo; + + // Properties. + PlayerProperties m_properties; + + // Data ready event. + NewDataEvent m_newDataEvent; + + int m_requiredFrameSize; +}; + +} // namespace oni_files_player + +#endif //__PLAYER_DEVICE_H__ diff --git a/Source/Drivers/OniFile/PlayerStream.cpp b/Source/Drivers/OniFile/PlayerStream.cpp new file mode 100644 index 0000000..9803f55 --- /dev/null +++ b/Source/Drivers/OniFile/PlayerStream.cpp @@ -0,0 +1,249 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the definition of Device class that implements a virtual OpenNI +/// device, capable of reading data from a *.ONI file. + +#include "PlayerStream.h" +#include "PlayerSource.h" +#include "XnMemory.h" +#include "OniProperties.h" +#include "XnPlatform.h" +#include + +namespace oni_file { + +PlayerStream::PlayerStream(PlayerSource* pSource) : + m_pSource(pSource), m_newDataHandle(NULL), m_isStarted(false), m_requiredFrameSize(0) +{ +} + +/// Destructor. +PlayerStream::~PlayerStream() +{ + // Destroy the stream (if it was not destroyed before). + destroy(); +} + +OniStatus PlayerStream::Initialize() +{ + // Register events in the source. + OniStatus rc = m_pSource->RegisterNewDataEvent(OnNewDataCallback, this, m_newDataHandle); + if (rc != ONI_STATUS_OK) + { + destroy(); + return rc; + } + + return ONI_STATUS_OK; +} + +void PlayerStream::destroy() +{ + stop(); + + if (m_newDataHandle != NULL) + { + // Send the destroy event. + DestroyEventArgs destroyEventArgs; + destroyEventArgs.pStream = this; + m_destroyEvent.Raise(destroyEventArgs); + + // Unregister from events. + m_pSource->UnregisterNewDataEvent(m_newDataHandle); + m_newDataHandle = NULL; + } +} + +OniStatus PlayerStream::start() +{ + m_isStarted = true; + m_requiredFrameSize = getRequiredFrameSize(); + return ONI_STATUS_OK; +} + +void PlayerStream::stop() +{ + m_isStarted = false; +} + +PlayerSource* PlayerStream::GetSource() +{ + return m_pSource; +} + +OniStatus PlayerStream::getProperty(int propertyId, void* pData, int* pDataSize) +{ + // Check if the property exists. + m_cs.Lock(); + OniStatus rc = m_properties.GetProperty(propertyId, pData, pDataSize); + if (rc != ONI_STATUS_OK) + { + rc = m_pSource->GetProperty(propertyId, pData, pDataSize); + } + m_cs.Unlock(); + + return rc; +} + +OniStatus PlayerStream::setProperty(int /*propertyId*/, const void* /*pData*/, int /*dataSize*/) +{ + return ONI_STATUS_ERROR; +} + +OniStatus PlayerStream::RegisterReadyForDataEvent(ReadyForDataCallback callback, void* pCookie, OniCallbackHandle& handle) +{ + XnStatus rc = m_readyForDataEvent.Register(callback, pCookie, (XnCallbackHandle&)handle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + return ONI_STATUS_OK; +} + +void PlayerStream::UnregisterReadyForDataEvent(OniCallbackHandle handle) +{ + m_readyForDataEvent.Unregister((XnCallbackHandle)handle); +} + +OniStatus PlayerStream::RegisterDestroyEvent(DestroyCallback callback, void* pCookie, OniCallbackHandle& handle) +{ + XnStatus rc = m_destroyEvent.Register(callback, pCookie, (XnCallbackHandle&)handle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + return ONI_STATUS_OK; +} + +void PlayerStream::UnregisterDestroyEvent(OniCallbackHandle handle) +{ + m_destroyEvent.Unregister((XnCallbackHandle)handle); +} + +void ONI_CALLBACK_TYPE PlayerStream::OnNewDataCallback(const PlayerSource::NewDataEventArgs& newDataEventArgs, void* pCookie) +{ + PlayerStream* pStream = (PlayerStream*)pCookie; + + // Don't process new frames until the stream is started. + if(!pStream->m_isStarted) + { + return; + } + + // Get the video mode. + OniVideoMode videoMode; + int valueSize = sizeof(videoMode); + OniStatus rc = pStream->m_pSource->GetProperty(ONI_STREAM_PROPERTY_VIDEO_MODE, &videoMode, &valueSize); + if (rc != ONI_STATUS_OK) + { + XN_ASSERT(FALSE); + return; + } + + // Get stride. + int stride; + valueSize = sizeof(stride); + rc = pStream->m_pSource->GetProperty(ONI_STREAM_PROPERTY_STRIDE, &stride, &valueSize); + if (rc != ONI_STATUS_OK) + { + XN_ASSERT(FALSE); + return; + } + + // Set the cropping property. + OniCropping cropping; + cropping.enabled = FALSE; + int dataSize = sizeof(cropping); + rc = pStream->m_pSource->GetProperty(ONI_STREAM_PROPERTY_CROPPING, &cropping, &dataSize); + if (rc != ONI_STATUS_OK) + { + XN_ASSERT(FALSE); + return; + } + + pStream->m_cs.Lock(); + + // Allocate new frame and fill it. + OniFrame* pFrame = pStream->getServices().acquireFrame(); + if (pFrame == NULL) + { + return; + } + + // Fill the frame. + pFrame->frameIndex = newDataEventArgs.nFrameId; + + pFrame->videoMode.pixelFormat = videoMode.pixelFormat; + pFrame->videoMode.resolutionX = videoMode.resolutionX; + pFrame->videoMode.resolutionY = videoMode.resolutionY; + pFrame->videoMode.fps = videoMode.fps; + if (!cropping.enabled) + { + // Set the full resolution, stride and origin. + pFrame->width = videoMode.resolutionX; + pFrame->height = videoMode.resolutionY; + pFrame->stride = stride; + pFrame->cropOriginX = 0; + pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + } + else + { + // Take resolution, and origin from cropping and calculate new stride. + pFrame->width = cropping.width; + pFrame->height = cropping.height; + pFrame->stride = (stride / pFrame->videoMode.resolutionX) * cropping.width; + pFrame->cropOriginX = cropping.originX; + pFrame->cropOriginY = cropping.originY; + pFrame->croppingEnabled = TRUE; + } + pFrame->sensorType = pStream->m_pSource->GetInfo()->sensorType; + pFrame->timestamp = newDataEventArgs.nTimeStamp; + pFrame->dataSize = newDataEventArgs.nSize; + if (pFrame->dataSize > pStream->m_requiredFrameSize) + { + xnLogWarning("Player", "File contains a frame with size %d whereas required frame size is %d", pFrame->dataSize, pStream->m_requiredFrameSize); + XN_ASSERT(FALSE); + pFrame->dataSize = pStream->m_requiredFrameSize; + } + memcpy(pFrame->data, newDataEventArgs.pData, pFrame->dataSize); + + pStream->m_cs.Unlock(); + + // Process the new frame. + pStream->raiseNewFrame(pFrame); + pStream->getServices().releaseFrame(pFrame); +} + +int PlayerStream::getRequiredFrameSize() +{ + int requiredFrameSize = m_pSource->GetRequiredFrameSize(); + if (requiredFrameSize == 0) + { + // not set for some reason, use default one + requiredFrameSize = StreamBase::getRequiredFrameSize(); + } + + return requiredFrameSize; +} + +} // namespace oni_files_player diff --git a/Source/Drivers/OniFile/PlayerStream.h b/Source/Drivers/OniFile/PlayerStream.h new file mode 100644 index 0000000..e8e36c9 --- /dev/null +++ b/Source/Drivers/OniFile/PlayerStream.h @@ -0,0 +1,140 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/// @file +/// Contains the declaration of Stream class that implements a stream from +/// a virtual OpenNI device. + +#ifndef __PLAYER_STREAM_H__ +#define __PLAYER_STREAM_H__ + +#include "Driver/OniDriverAPI.h" +#include "PlayerProperties.h" +#include "PlayerSource.h" +#include "XnOSCpp.h" + +namespace oni_file { + +class Decoder; + +/// Implements a stream from a virtual OpenNI device. +class PlayerStream : public oni::driver::StreamBase +{ +public: + + // General stream event. + typedef struct + { + PlayerStream* pStream; + } StreamEventArgs; + typedef xnl::Event StreamEvent; + typedef void (ONI_CALLBACK_TYPE* StreamCallback)(const StreamEventArgs& streamEventArgs, void* pCookie); + + // Ready for data event. + typedef StreamEventArgs ReadyForDataEventArgs; + typedef StreamEvent ReadyForDataEvent; + typedef StreamCallback ReadyForDataCallback; + + // Destroy event. + typedef StreamEventArgs DestroyEventArgs; + typedef StreamEvent DestroyEvent; + typedef StreamCallback DestroyCallback; + +public: + /// Constructor. + PlayerStream(PlayerSource* pSource); + + /// Destructor. + virtual ~PlayerStream(); + + /// Initialize the stream object. + OniStatus Initialize(); + + virtual int getRequiredFrameSize(); + + /// @copydoc OniStreamBase::start() + virtual OniStatus start(); + + /// has start() been called on this stream already? + bool isStreamStarted() { return m_isStarted; } + + /// @copydoc OniStreamBase::stop() + virtual void stop(); + + // Return the player source the stream was created on. + PlayerSource* GetSource(); + + /// @copydoc OniStreamBase::getProperty(int,void*,int*) + virtual OniStatus getProperty(int propertyId, void* pData, int* pDataSize); + + /// @copydoc OniStreamBase::setProperty(int,void*,int*) + virtual OniStatus setProperty(int propertyId, const void* pData, int dataSize); + + // Register for 'ready for data' event. + OniStatus RegisterReadyForDataEvent(ReadyForDataCallback callback, void* pCookie, OniCallbackHandle& handle); + + // Unregister from 'ready for data' event. + void UnregisterReadyForDataEvent(OniCallbackHandle handle); + + // Register for 'destroy' event. + OniStatus RegisterDestroyEvent(DestroyCallback callback, void* pCookie, OniCallbackHandle& handle); + + // Unregister from 'destroy' event. + void UnregisterDestroyEvent(OniCallbackHandle handle); + +private: + void destroy(); + + void MainLoop(); + static XN_THREAD_PROC ThreadProc(XN_THREAD_PARAM pThreadParam); + + // Callback to be called when new data is available. + static void ONI_CALLBACK_TYPE OnNewDataCallback(const PlayerSource::NewDataEventArgs& newDataEventArgs, void* pCookie); + +// Data members +private: + + // Source the stream was created on. + PlayerSource* m_pSource; + + // Stream properties. + PlayerProperties m_properties; + + // Handle to new data callback. + OniCallbackHandle m_newDataHandle; + + // Ready for data event. + ReadyForDataEvent m_readyForDataEvent; + + // Destroy event. + DestroyEvent m_destroyEvent; + + // Critical section. + xnl::CriticalSection m_cs; + + // Are we streaming right now? + bool m_isStarted; + + int m_requiredFrameSize; +}; + +} // namespace oni_files_player + +#endif //__PLAYER_STREAM_H__ diff --git a/Source/Drivers/OniFile/XnPlayerTypes.h b/Source/Drivers/OniFile/XnPlayerTypes.h new file mode 100644 index 0000000..9d82242 --- /dev/null +++ b/Source/Drivers/OniFile/XnPlayerTypes.h @@ -0,0 +1,365 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PLAYER_TYPES_H__ +#define __XN_PLAYER_TYPES_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- + +/** The maximum length of names of objects in OpenNI **/ +#define XN_MAX_NAME_LENGTH 80 + +/** The name of the OpenNI recording format. **/ +#define XN_FORMAT_NAME_ONI "oni" + +//--------------------------------------------------------------------------- +// Forward Declarations +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** + * Type of the production node. + */ +typedef XnInt32 XnProductionNodeType; + +/** + * Predefined types of production nodes. + */ +typedef enum XnPredefinedProductionNodeType +{ + /** An invalid node type **/ + XN_NODE_TYPE_INVALID = -1, + + /** A device node **/ + XN_NODE_TYPE_DEVICE = 1, + + /** A depth generator **/ + XN_NODE_TYPE_DEPTH = 2, + + /** An image generator **/ + XN_NODE_TYPE_IMAGE = 3, + + /** An audio generator **/ + XN_NODE_TYPE_AUDIO = 4, + + /** An IR generator **/ + XN_NODE_TYPE_IR = 5, + + /** A user generator **/ + XN_NODE_TYPE_USER = 6, + + /** A recorder **/ + XN_NODE_TYPE_RECORDER = 7, + + /** A player **/ + XN_NODE_TYPE_PLAYER = 8, + + /** A gesture generator **/ + XN_NODE_TYPE_GESTURE = 9, + + /** A scene analyzer **/ + XN_NODE_TYPE_SCENE = 10, + + /** A hands generator **/ + XN_NODE_TYPE_HANDS = 11, + + /** A Codec **/ + XN_NODE_TYPE_CODEC = 12, + + /** Abstract types **/ + XN_NODE_TYPE_PRODUCTION_NODE = 13, + XN_NODE_TYPE_GENERATOR = 14, + XN_NODE_TYPE_MAP_GENERATOR = 15, + XN_NODE_TYPE_SCRIPT = 16, + + XN_NODE_TYPE_FIRST_EXTENSION, + +} XnPredefinedProductionNodeType; + +/** + * A Version. + */ +typedef struct XnVersion +{ + XnUInt8 nMajor; + XnUInt8 nMinor; + XnUInt16 nMaintenance; + XnUInt32 nBuild; +} XnVersion; + +//--------------------------------------------------------------------------- +// 3D Vision Types +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Generators Capabilities +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Generators API Structs +//--------------------------------------------------------------------------- + +/** + * The output mode of a map generator. + */ +typedef struct XnMapOutputMode +{ + /** Number of elements in the X-axis. */ + XnUInt32 nXRes; + /** Number of elements in the Y-axis. */ + XnUInt32 nYRes; + /** Number of frames per second. */ + XnUInt32 nFPS; +} XnMapOutputMode; + +/** + * Cropping configuration + */ +typedef struct XnCropping +{ + /** TRUE if cropping is turned on, FALSE otherwise. */ + XnBool bEnabled; + /** Offset in the X-axis, in pixels. */ + XnUInt16 nXOffset; + /** Offset in the Y-axis, in pixels. */ + XnUInt16 nYOffset; + /** Number of pixels in the X-axis. */ + XnUInt16 nXSize; + /** Number of pixels in the Y-axis. */ + XnUInt16 nYSize; +} XnCropping; + +/** + * Field-Of-View + */ +typedef struct XnFieldOfView +{ + /** Horizontal Field Of View, in radians. */ + XnDouble fHFOV; + /** Vertical Field Of View, in radians. */ + XnDouble fVFOV; +} XnFieldOfView; + +typedef enum XnPixelFormat +{ + XN_PIXEL_FORMAT_RGB24 = 1, + XN_PIXEL_FORMAT_YUV422 = 2, + XN_PIXEL_FORMAT_GRAYSCALE_8_BIT = 3, + XN_PIXEL_FORMAT_GRAYSCALE_16_BIT = 4, + XN_PIXEL_FORMAT_MJPEG = 5, +} XnPixelFormat; + +typedef enum XnPlayerSeekOrigin +{ + XN_PLAYER_SEEK_SET = 0, + XN_PLAYER_SEEK_CUR = 1, + XN_PLAYER_SEEK_END = 2, +} XnPlayerSeekOrigin; + +//--------------------------------------------------------------------------- +// Recorder Types +//--------------------------------------------------------------------------- + +/** An ID of a codec. See @ref xnCreateCodec. **/ +typedef XnUInt32 XnCodecID; + +/** Define a Codec ID by 4 characters, e.g. XN_CODEC_ID('J','P','E','G') **/ +#define XN_CODEC_ID(c1, c2, c3, c4) (XnCodecID)((c4 << 24) | (c3 << 16) | (c2 << 8) | c1) + +/** + * An interface used for communication between OpenNI and a player module. This interface is used by a player + * module to receive recorded data from OpenNI, which knows where to get them according to one of the values of + * @ref XnRecordMedium. + **/ +typedef struct XnPlayerInputStreamInterface +{ + /** + * Opens the stream for reading. + * + * @param pCookie [in] A cookie that was received with this interface. + */ + XnStatus (XN_CALLBACK_TYPE* Open)(void* pCookie); + + /** + * Reads data from the stream. May read less data than asked, if the stream is near its end. This is not + * considered an error. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param pBuffer [out] A pointer to the buffer to read into. + * @param nSize [in] Number of bytes to read. + * @param pnBytesRead [out] Optional. Number of bytes actually read. + */ + XnStatus (XN_CALLBACK_TYPE* Read)(void* pCookie, void* pBuffer, XnUInt32 nSize, XnUInt32* pnBytesRead); + + /** + * Sets the stream's pointer to the specified position. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param seekType [in] Specifies how to seek - according to current position, end or beginning. + * @param nOffset [in] Specifies how many bytes to move + */ + XnStatus (XN_CALLBACK_TYPE* Seek)(void* pCookie, XnOSSeekType seekType, const XnInt32 nOffset); + + /** + * Tells the current stream position + * + * @param pCookie [in] A cookie that was received with this interface. + * + * @returns (XnUInt32)-1 if there was an error in the stream. + */ + XnUInt32 (XN_CALLBACK_TYPE* Tell)(void* pCookie); + + /** + * Closes the stream. + * + * @param pCookie [in] A cookie that was received with this interface. + */ + void (XN_CALLBACK_TYPE* Close)(void* pCookie); + + /** + * Sets the stream's pointer to the specified position. (64bit version, for large files) + * + * @param pCookie [in] A cookie that was received with this interface. + * @param seekType [in] Specifies how to seek - according to current position, end or beginning. + * @param nOffset [in] Specifies how many bytes to move + */ + XnStatus (XN_CALLBACK_TYPE* Seek64)(void* pCookie, XnOSSeekType seekType, const XnInt64 nOffset); + + /** + * Tells the current position in the stream. (64bit version, for large files) + * + * @param pCookie [in] A cookie that was received with this interface. + * @param pPos [out] The position of the stream. + * + * @returns (XnUInt64)-1 on error. + */ + XnUInt64 (XN_CALLBACK_TYPE* Tell64)(void* pCookie); + +} XnPlayerInputStreamInterface; + +/** + * An interface that is used for notifications about node events. + **/ +typedef struct XnNodeNotifications +{ + /** + * Notifies the object that a production node was added + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The instance name of the added node. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeAdded) + (void* pCookie, const XnChar* strNodeName, XnProductionNodeType type, + XnCodecID compression, XnUInt32 nNumberOfFrames); + + /** + * Notifies the object that a production node has been removed + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The instance name of the removed node. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeRemoved) + (void* pCookie, const XnChar* strNodeName); + + /** + * Notifies the object that an integer type property has changed. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The name of the node whose property changed. + * @param strPropName [in] The name of the property that changed. + * @param nValue [in] The new value of the property. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeIntPropChanged) + (void* pCookie, const XnChar* strNodeName, + const XnChar* strPropName, XnUInt64 nValue); + + /** + * Notifies the object that a real type property has changed. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The name of the node whose property changed. + * @param strPropName [in] The name of the property that changed. + * @param dValue [in] The new value of the property. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeRealPropChanged) + (void* pCookie, const XnChar* strNodeName, + const XnChar* strPropName, XnDouble dValue); + + /** + * Notifies the object that a string type property has changed. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The name of the node whose property changed. + * @param strPropName [in] The name of the property that changed. + * @param strValue [in] The new value of the property. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeStringPropChanged) + (void* pCookie, const XnChar* strNodeName, + const XnChar* strPropName, const XnChar* strValue); + + /** + * Notifies the object that a general type property has changed. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The name of the node whose property changed. + * @param strPropName [in] The name of the property that changed. + * @param nBufferSize [in] The size of the buffer that holds the new value. + * @param pBuffer [in] The buffer that holds the new value of the property. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeGeneralPropChanged) + (void* pCookie, const XnChar* strNodeName, + const XnChar* strPropName, XnUInt32 nBufferSize, const void* pBuffer); + + /** + * Notifies the object that a node has finished sending all the initial 'property changed' notifications. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The name of the node whose state is ready. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeStateReady) + (void* pCookie, const XnChar* strNodeName); + + /** + * Notifies the object that it has received new data. + * + * @param pCookie [in] A cookie that was received with this interface. + * @param strNodeName [in] The name of the node whose property changed. + * @param strName [in] The name of the property that changed. + * @param nBufferSize [in] The size of the buffer that holds the new value. + * @param pBuffer [in] The buffer that holds the new value of the property. + */ + XnStatus (XN_CALLBACK_TYPE* OnNodeNewData) + (void* pCookie, const XnChar* strNodeName, + XnUInt64 nTimeStamp, XnUInt32 nFrame, const void* pData, XnUInt32 nSize); + +} XnNodeNotifications; + +#endif //__XN_PLAYER_TYPES_H__ diff --git a/Source/Drivers/OniFile/XnPropNames.h b/Source/Drivers/OniFile/XnPropNames.h new file mode 100644 index 0000000..7019a6d --- /dev/null +++ b/Source/Drivers/OniFile/XnPropNames.h @@ -0,0 +1,65 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PROP_NAMES_H__ +#define __XN_PROP_NAMES_H__ + +//ProductionNode +#define XN_PROP_STATE_READY "xnStateReady" // int. Meant only for mock nodes. + +//Generator +#define XN_PROP_IS_GENERATING "xnIsGenerating" //int +#define XN_PROP_MIRROR "xnMirror" //int +#define XN_PROP_TIMESTAMP "xnTimeStamp" //int +#define XN_PROP_FRAME_ID "xnFrameID" //int +#define XN_PROP_NEWDATA "xnNewData" //general. Meant only for mock nodes. +#define XN_PROP_FRAME_SYNCED_WITH "xnFrameSyncedWith" // String. name of the frame synced + +//MapGenerator +#define XN_PROP_MAP_OUTPUT_MODE "xnMapOutputMode" //general +#define XN_PROP_SUPPORTED_MAP_OUTPUT_MODES_COUNT "xnSupportedMapOutputModesCount" //int +#define XN_PROP_SUPPORTED_MAP_OUTPUT_MODES "xnSupportedMapOutputModes" //general +#define XN_PROP_CROPPING "xnCropping" //general +#define XN_PROP_BYTES_PER_PIXEL "xnBytesPerPixel" //int + +//ImageGenerator +#define XN_PROP_SUPPORTED_PIXEL_FORMATS "xnSupportedPixelFormats" //general +#define XN_PROP_PIXEL_FORMAT "xnPixelFormat" //int + +//GestureGenerator +#define XN_PROP_GESTURE_RECOGNIZED "xnGestureRecognized" //general +#define XN_PROP_GESTURE_PROGRESS "xnGestureProgress" //general + +//DepthGenerator +#define XN_PROP_DEVICE_MAX_DEPTH "xnDeviceMaxDepth" //int +#define XN_PROP_SUPPORTED_USER_POSITIONS_COUNT "xnSupportedUserPositionsCount" //int +#define XN_PROP_USER_POSITIONS "xnUserPositions" //general +#define XN_PROP_FIELD_OF_VIEW "xnFOV" // general (XnFieldOfView) + +//AudioGenerator +#define XN_PROP_WAVE_OUTPUT_MODE "xnWaveOutputMode" //general +#define XN_PROP_WAVE_SUPPORTED_OUTPUT_MODES_COUNT "xnWaveSupportedOutputModesCount" //int +#define XN_PROP_WAVE_SUPPORTED_OUTPUT_MODES "xnWaveSupportedOutputModes" //general + +// New OpenNI 2.0 properties +#define XN_PROP_ONI_PIXEL_FORMAT "oniPixelFormat" +#define XN_PROP_ONI_REQUIRED_FRAME_SIZE "oniRequiredFrameSize" + +#endif //__XN_PROP_NAMES_H__ diff --git a/Source/Drivers/PS1080/Android.mk b/Source/Drivers/PS1080/Android.mk new file mode 100644 index 0000000..4525041 --- /dev/null +++ b/Source/Drivers/PS1080/Android.mk @@ -0,0 +1,67 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Sources +MY_SRC_FILES := \ + $(LOCAL_PATH)/Core/*.cpp \ + $(LOCAL_PATH)/DDK/*.cpp \ + $(LOCAL_PATH)/DriverImpl/*.cpp\ + $(LOCAL_PATH)/Formats/*.cpp \ + $(LOCAL_PATH)/Include/*.cpp \ + $(LOCAL_PATH)/Sensor/*.cpp + +ifdef OPENNI2_ANDROID_NDK_BUILD + MY_SRC_FILES += $(LOCAL_PATH)/../../../ThirdParty/LibJPEG/*.c +endif + +MY_SRC_FILE_EXPANDED := $(wildcard $(MY_SRC_FILES)) +LOCAL_SRC_FILES := $(MY_SRC_FILE_EXPANDED:$(LOCAL_PATH)/%=%) + +# C/CPP Flags +LOCAL_CFLAGS += $(OPENNI2_CFLAGS) +LOCAL_CPPFLAGS := -frtti + +# Includes +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/. \ + $(LOCAL_PATH)/Include \ + $(LOCAL_PATH)/../../DepthUtils \ + $(LOCAL_PATH)/../../../Include \ + $(LOCAL_PATH)/../../../ThirdParty/PSCommon/XnLib/Include + +ifdef OPENNI2_ANDROID_NDK_BUILD + LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../ThirdParty/LibJPEG +else + LOCAL_C_INCLUDES += external/jpeg +endif + +# Dependencies +LOCAL_STATIC_LIBRARIES := XnLib DepthUtils +LOCAL_SHARED_LIBRARIES := liblog libdl libusb libgabi++ + +ifdef OPENNI2_ANDROID_OS_BUILD + LOCAL_SHARED_LIBRARIES += libjpeg +else + LOCAL_LDLIBS += -llog +endif + +# Output +LOCAL_MODULE:= libPS1080 + +include $(BUILD_SHARED_LIBRARY) diff --git a/Source/Drivers/PS1080/Core/XnBuffer.cpp b/Source/Drivers/PS1080/Core/XnBuffer.cpp new file mode 100644 index 0000000..ab73475 --- /dev/null +++ b/Source/Drivers/PS1080/Core/XnBuffer.cpp @@ -0,0 +1,56 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnBuffer.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnBuffer::Allocate(XnUInt32 nAllocSize) +{ + Free(); + XN_VALIDATE_ALIGNED_CALLOC(m_pData, XnUChar, nAllocSize, XN_DEFAULT_MEM_ALIGN); + m_nMaxSize = nAllocSize; + m_nSize = 0; + m_bAllocated = TRUE; + return (XN_STATUS_OK); +} + +void XnBuffer::SetExternalBuffer(XnUChar* pBuffer, XnUInt32 nSize) +{ + Free(); + m_pData = pBuffer; + m_nMaxSize = nSize; + m_nSize = 0; + m_bAllocated = FALSE; +} + +XnStatus XnBuffer::Write(const XnUChar* pData, XnUInt32 nDataSize) +{ + if (GetFreeSpaceInBuffer() < nDataSize) + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + + UnsafeWrite(pData, nDataSize); + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/Core/XnBuffer.h b/Source/Drivers/PS1080/Core/XnBuffer.h new file mode 100644 index 0000000..29fd630 --- /dev/null +++ b/Source/Drivers/PS1080/Core/XnBuffer.h @@ -0,0 +1,189 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_BUFFER_H__ +#define __XN_BUFFER_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +/** +* Holds a buffer of data, and provides some common methods for it. +*/ +class XnBuffer +{ +public: + XnBuffer() : m_pData(NULL), m_nSize(0), m_nMaxSize(0), m_bAllocated(FALSE) {} + ~XnBuffer() { Free(); } + + /* + * Allocates a buffer (aligned to default). + * + * @param size [in] The number of bytes to allocate. + */ + XnStatus Allocate(XnUInt32 nAllocSize); + + /** + * Sets an external buffer (instead of allocating) + * + * @param pBuffer [in] the buffer to use. + * @param nSize [in] Buffer size. + */ + void SetExternalBuffer(XnUChar* pBuffer, XnUInt32 nSize); + + /* + * Writes data to the buffer. + * + * @param data [in] a pointer to the data to copy from. + * @param size [in] The number of bytes to copy. + */ + inline void UnsafeWrite(const XnUChar* pData, XnUInt32 nDataSize) + { + // Some of my callers copy data from within my buffer after Reset()ing. + // So safely "slide" the to-be-copied chunk back to the buffer's beginning. + xnOSMemMove(m_pData + m_nSize, pData, nDataSize); + m_nSize += nDataSize; + } + + /* + * Writes data to the buffer, checking for free space + * + * @param data [in] a pointer to the data to copy from. + * @param size [in] The number of bytes to copy. + */ + XnStatus Write(const XnUChar* pData, XnUInt32 nDataSize); + + /* + * Copies buffer data to another location. + * + * @param dest [in] Location to write to. + */ + inline void UnsafeCopy(void* pDest) + { + xnOSMemCopy(pDest, m_pData, m_nSize); + } + + /* + * Empties the buffer. + */ + inline void Reset() + { + m_nSize = 0; + } + + inline XnUChar* GetData() + { + return m_pData; + } + + inline XnUInt32 GetSize() + { + return m_nSize; + } + + inline XnUInt32 GetMaxSize() + { + return m_nMaxSize; + } + + /* + * Frees an allocated buffer. + */ + inline void Free() + { + if (m_bAllocated) + { + XN_ALIGNED_FREE_AND_NULL(m_pData); + m_bAllocated = FALSE; + } + } + + /* + * Gets the free space in the buffer. + */ + inline XnUInt32 GetFreeSpaceInBuffer() + { + return XN_MAX(0, (XnInt32)(m_nMaxSize - m_nSize)); + } + + /* + * Gets the free space in the buffer. + */ + inline XnUChar* GetUnsafeWritePointer() + { + return m_pData + m_nSize; + } + + /* + * Updates the size of the buffer + */ + inline void UnsafeUpdateSize(XnUInt32 nWrittenBytes) + { + m_nSize += nWrittenBytes; + } + + inline void UnsafeSetSize(XnUInt32 nSize) + { + m_nSize = nSize; + } + + /* + * Update the user cookie for this buffer. + */ + inline void SetCookie(void* pCookie) + { + m_pCookie = pCookie; + } + + /* + * Retrieve the user cookie for this buffer. + */ + inline void* GetCookie() + { + return m_pCookie; + } + +private: + XnUChar* m_pData; + XnUInt32 m_nSize; + XnUInt32 m_nMaxSize; + XnBool m_bAllocated; + void* m_pCookie; +}; + +/* +* Allocates a buffer (aligned to default), and validates that allocation succeeded. +* +* @param buf [in] The buffer to allocate. +* @param size [in] The number of bytes to allocate. +*/ +#define XN_VALIDATE_BUFFER_ALLOCATE(buf, size) \ + { \ + XnStatus rc = buf.Allocate(size); \ + XN_IS_STATUS_OK(rc); \ + } + +#endif //__XN_BUFFER_H__ diff --git a/Source/Drivers/PS1080/Core/XnCore.cpp b/Source/Drivers/PS1080/Core/XnCore.cpp new file mode 100644 index 0000000..b44ca70 --- /dev/null +++ b/Source/Drivers/PS1080/Core/XnCore.cpp @@ -0,0 +1,83 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Global Variables +//--------------------------------------------------------------------------- +// Note: See the XnIOGlobals.h file for global variables description +XnBool g_bXnCoreWasInit = FALSE; + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnStatus XnGeneralBufferCopy(OniGeneralBuffer* pDest, const OniGeneralBuffer* pSrc) +{ + XN_VALIDATE_INPUT_PTR(pDest); + XN_VALIDATE_INPUT_PTR(pSrc); + + if (pSrc->dataSize > pDest->dataSize) + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + + xnOSMemCopy(pDest->data, pSrc->data, pSrc->dataSize); + pDest->dataSize = pSrc->dataSize; + return XN_STATUS_OK; +} + +XnStatus XnGeneralBufferAlloc(OniGeneralBuffer* pDest, XnUInt32 nSize) +{ + XN_VALIDATE_INPUT_PTR(pDest); + + void* pData; + pData = xnOSMalloc(nSize); + XN_VALIDATE_ALLOC_PTR(pData); + + pDest->data = pData; + pDest->dataSize = nSize; + return XN_STATUS_OK; +} + +XnStatus XnGeneralBufferRealloc(OniGeneralBuffer* pDest, XnUInt32 nSize) +{ + XN_VALIDATE_INPUT_PTR(pDest); + + void* pData; + pData = xnOSRealloc(pDest, nSize); + XN_VALIDATE_ALLOC_PTR(pData); + + pDest->data = pData; + pDest->dataSize = nSize; + return XN_STATUS_OK; +} + +void XnGeneralBufferFree(OniGeneralBuffer* pDest) +{ + XN_FREE_AND_NULL(pDest->data); + pDest->dataSize = 0; +} diff --git a/Source/Drivers/PS1080/Core/XnCoreGlobals.h b/Source/Drivers/PS1080/Core/XnCoreGlobals.h new file mode 100644 index 0000000..347ff16 --- /dev/null +++ b/Source/Drivers/PS1080/Core/XnCoreGlobals.h @@ -0,0 +1,58 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_COREGLOBALS_H_ +#define _XN_COREGLOBALS_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +/** The Xiron I/O INI section name. */ +#define XN_CORE_INI_SECTION "Core" + +//--------------------------------------------------------------------------- +// Global Variables +//--------------------------------------------------------------------------- +/** Was the Xiron Core subsystem successfully initialized? */ +extern XnBool g_bXnCoreWasInit; + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +/** Make sure the core subsystem was initialized. */ +#define XN_VALIDATE_CORE_INIT() \ + if (g_bXnCoreWasInit != TRUE) \ + { \ + return (XN_STATUS_NOT_INIT); \ + } + +/** Make sure the core subsystem was not initialized already. */ +#define XN_VALIDATE_CORE_NOT_INIT() \ + if (g_bXnCoreWasInit == TRUE) \ + { \ + return (XN_STATUS_ALREADY_INIT); \ + } + +#endif //_XN_COREGLOBALS_H_ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Core/XnCoreStatus.cpp b/Source/Drivers/PS1080/Core/XnCoreStatus.cpp new file mode 100644 index 0000000..f157727 --- /dev/null +++ b/Source/Drivers/PS1080/Core/XnCoreStatus.cpp @@ -0,0 +1,27 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +// registration is done by including XnStatusRegister *before* including the list of errors +#include +#define XN_MESSAGE_MAP_REGISTER +#include "XnCore.h" diff --git a/Source/Drivers/PS1080/Core/XnIOFileStream.cpp b/Source/Drivers/PS1080/Core/XnIOFileStream.cpp new file mode 100644 index 0000000..ffaeff3 --- /dev/null +++ b/Source/Drivers/PS1080/Core/XnIOFileStream.cpp @@ -0,0 +1,78 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnIOFileStream::XnIOFileStream(const XnChar* pcsFileName, XnUInt32 nFlags) : + m_pcsFileName(pcsFileName), m_nFlags(nFlags) +{ +} + +XnStatus XnIOFileStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSOpenFile(m_pcsFileName, m_nFlags, &m_hFile); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnIOFileStream::Free() +{ + return xnOSCloseFile(&m_hFile); +} + +XnStatus XnIOFileStream::WriteData(const XnUChar *pData, XnUInt32 nDataSize) +{ + return xnOSWriteFile(m_hFile, pData, nDataSize); +} + +XnStatus XnIOFileStream::ReadData(XnUChar *pData, XnUInt32 nDataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt32 nReadSize = nDataSize; + nRetVal = xnOSReadFile(m_hFile, pData, &nReadSize); + XN_IS_STATUS_OK(nRetVal); + + if (nReadSize != nDataSize) + { + return XN_STATUS_OS_FILE_READ_FAILED; + } + + return (XN_STATUS_OK); +} + +XnStatus XnIOFileStream::Tell(XnUInt64* pnOffset) +{ + return xnOSTellFile64(m_hFile, pnOffset); +} + +XnStatus XnIOFileStream::Seek(XnUInt64 nOffset) +{ + return xnOSSeekFile64(m_hFile, XN_OS_SEEK_SET, nOffset); +} diff --git a/Source/Drivers/PS1080/Core/XnIONetworkStream.cpp b/Source/Drivers/PS1080/Core/XnIONetworkStream.cpp new file mode 100644 index 0000000..de18e34 --- /dev/null +++ b/Source/Drivers/PS1080/Core/XnIONetworkStream.cpp @@ -0,0 +1,101 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnIONetworkStream.h" +#include + +#define XN_MASK_IO_NET_STREAM "IoNetStream" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnIONetworkStream::XnIONetworkStream(XN_SOCKET_HANDLE hSocket) : + m_nReadTimeout(XN_WAIT_INFINITE), + m_hSocket(hSocket), + m_bIsConnected(TRUE) +{ +} + +XnStatus XnIONetworkStream::Init() +{ + return (XN_STATUS_OK); +} + +XnStatus XnIONetworkStream::Free() +{ + m_bIsConnected = FALSE; + //We don't close the socket here because we don't own it - whoever opened it should be the one to close it. + return XN_STATUS_OK; +} + +XnStatus XnIONetworkStream::WriteData(const XnUChar *pData, XnUInt32 nDataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSSendNetworkBuffer(m_hSocket, (const XnChar*)pData, nDataSize); + if (nRetVal != XN_STATUS_OK) + { + m_bIsConnected = FALSE; + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnIONetworkStream::ReadData(XnUChar *pData, XnUInt32 nDataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt32 nTotalRead = 0; + + // read until we get all the data we want + while (nTotalRead < nDataSize) + { + XnUInt32 nReadSize = nDataSize - nTotalRead; + nRetVal = xnOSReceiveNetworkBuffer(m_hSocket, (XnChar*)(pData + nTotalRead), &nReadSize, m_nReadTimeout); + if (nRetVal != XN_STATUS_OK) + { + if (nRetVal == XN_STATUS_OS_NETWORK_CONNECTION_CLOSED) + { + xnLogVerbose(XN_MASK_IO_NET_STREAM, "Network connection was closed gracefully"); + m_bIsConnected = FALSE; + } + else if (nRetVal != XN_STATUS_OS_NETWORK_TIMEOUT) + { + xnLogError(XN_MASK_IO_NET_STREAM, "Got an error while reading network buffer: %s", xnGetStatusString(nRetVal)); + m_bIsConnected = FALSE; + } + return (nRetVal); + } + + nTotalRead += nReadSize; + } + + return (XN_STATUS_OK); +} + + +void XnIONetworkStream::SetReadTimeout(XnUInt32 nMicrosecondsReadTimeout) +{ + m_nReadTimeout = nMicrosecondsReadTimeout; +} diff --git a/Source/Drivers/PS1080/DDK/XnActualGeneralProperty.cpp b/Source/Drivers/PS1080/DDK/XnActualGeneralProperty.cpp new file mode 100644 index 0000000..64369b7 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualGeneralProperty.cpp @@ -0,0 +1,86 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnActualGeneralProperty.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnActualGeneralProperty::XnActualGeneralProperty(XnUInt32 propertyId, const XnChar* strName, void* pData, XnUInt32 nDataSize, ReadValueFromFileFuncPtr pReadFromFileFunc /* = NULL */, const XnChar* strModule /* = "" */) : + XnGeneralProperty(propertyId, strName, &m_gbValue, pReadFromFileFunc, strModule), + m_gbValue(XnGeneralBufferPack(pData, nDataSize)), + m_bOwner(FALSE) +{ + // set a callback for get operations + UpdateGetCallback(GetCallback, this); +} + +XnActualGeneralProperty::XnActualGeneralProperty(XnUInt32 propertyId, const XnChar* strName, const OniGeneralBuffer& gbValue, ReadValueFromFileFuncPtr pReadFromFileFunc /* = NULL */, const XnChar* strModule /* = "" */) : + XnGeneralProperty(propertyId, strName, &m_gbValue, pReadFromFileFunc, strModule), + m_gbValue(gbValue), + m_bOwner(FALSE) +{ + // set a callback for get operations + UpdateGetCallback(GetCallback, this); +} + +XnActualGeneralProperty::~XnActualGeneralProperty() +{ + if (m_bOwner) + { + XnGeneralBufferFree(&m_gbValue); + } +} + +void XnActualGeneralProperty::SetAsBufferOwner(XnBool bOwner) +{ + m_bOwner = bOwner; +} + +XnStatus XnActualGeneralProperty::SetCallback(XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* /*pCookie*/) +{ + return pSender->UnsafeUpdateValue(gbValue); +} + +XnStatus XnActualGeneralProperty::GetCallback(const XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* /*pCookie*/) +{ + if (gbValue.dataSize != pSender->GetValue().dataSize) + { + return XN_STATUS_DEVICE_PROPERTY_SIZE_DONT_MATCH; + } + + xnOSMemCopy(gbValue.data, pSender->GetValue().data, pSender->GetValue().dataSize); + return XN_STATUS_OK; +} + +XnStatus XnActualGeneralProperty::AddToPropertySet(XnPropertySet* pSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnPropertySetAddGeneralProperty(pSet, GetModule(), GetId(), &m_gbValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/DDK/XnActualGeneralProperty.h b/Source/Drivers/PS1080/DDK/XnActualGeneralProperty.h new file mode 100644 index 0000000..f341299 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualGeneralProperty.h @@ -0,0 +1,81 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ACTUAL_GENERAL_PROPERTY_H__ +#define __XN_ACTUAL_GENERAL_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Class +//--------------------------------------------------------------------------- + +/** +* A property of type general which actually holds a value. +*/ +class XnActualGeneralProperty : public XnGeneralProperty +{ +public: + XnActualGeneralProperty(XnUInt32 propertyId, const XnChar* strName, void* pData, XnUInt32 nDataSize, ReadValueFromFileFuncPtr pReadFromFileFunc = NULL, const XnChar* strModule = ""); + XnActualGeneralProperty(XnUInt32 propertyId, const XnChar* strName, const OniGeneralBuffer& gbValue, ReadValueFromFileFuncPtr pReadFromFileFunc = NULL, const XnChar* strModule = ""); + ~XnActualGeneralProperty(); + + void SetAsBufferOwner(XnBool bOwner); + + inline const OniGeneralBuffer& GetValue() const { return m_gbValue; } + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnGeneralProperty::UpdateSetCallback((XnGeneralProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateSetCallbackToDefault() + { + UpdateSetCallback(SetCallback, this); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnGeneralProperty::UpdateGetCallback((XnGeneralProperty::GetFuncPtr)pFunc, pCookie); + } + + inline void ReplaceBuffer(void* pData, XnUInt32 nDataSize) + { + m_gbValue.data = pData; + m_gbValue.dataSize = nDataSize; + } + + XnStatus AddToPropertySet(XnPropertySet* pSet); + +private: + static XnStatus XN_CALLBACK_TYPE SetCallback(XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetCallback(const XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + + OniGeneralBuffer m_gbValue; + XnBool m_bOwner; +}; + +#endif //__XN_ACTUAL_GENERAL_PROPERTY_H__ diff --git a/Source/Drivers/PS1080/DDK/XnActualIntProperty.cpp b/Source/Drivers/PS1080/DDK/XnActualIntProperty.cpp new file mode 100644 index 0000000..3ea91d6 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualIntProperty.cpp @@ -0,0 +1,46 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnActualIntProperty.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnActualIntProperty::XnActualIntProperty(XnUInt32 propertyId, const XnChar* strName, XnUInt64 nInitialValue /* = 0 */, const XnChar* strModule /* = "" */) : + XnIntProperty(propertyId, strName, &m_nValue, strModule), + m_nValue(nInitialValue) +{ + // set a callback for get operations + UpdateGetCallback(GetCallback, this); +} + +XnStatus XN_CALLBACK_TYPE XnActualIntProperty::SetCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* /*pCookie*/) +{ + return pSender->UnsafeUpdateValue(nValue); +} + +XnStatus XN_CALLBACK_TYPE XnActualIntProperty::GetCallback(const XnActualIntProperty* pSender, XnUInt64* pnValue, void* /*pCookie*/) +{ + *pnValue = pSender->GetValue(); + return XN_STATUS_OK; +} diff --git a/Source/Drivers/PS1080/DDK/XnActualIntProperty.h b/Source/Drivers/PS1080/DDK/XnActualIntProperty.h new file mode 100644 index 0000000..e843104 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualIntProperty.h @@ -0,0 +1,68 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ACTUAL_INT_PROPERTY_H__ +#define __XN_ACTUAL_INT_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** +* A property of type integer. +*/ +class XnActualIntProperty : public XnIntProperty +{ +public: + XnActualIntProperty(XnUInt32 propertyId, const XnChar* strName, XnUInt64 nInitialValue = 0, const XnChar* strModule = ""); + + inline XnUInt64 GetValue() const { return m_nValue; } + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnActualIntProperty* pSender, XnUInt64* pnValue, void* pCookie); + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnIntProperty::UpdateSetCallback((XnIntProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateSetCallbackToDefault() + { + UpdateSetCallback(SetCallback, this); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnIntProperty::UpdateGetCallback((XnIntProperty::GetFuncPtr)pFunc, pCookie); + } + +private: + static XnStatus XN_CALLBACK_TYPE SetCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetCallback(const XnActualIntProperty* pSender, XnUInt64* pnValue, void* pCookie); + + XnUInt64 m_nValue; +}; + +#endif //__XN_ACTUAL_INT_PROPERTY_H__ diff --git a/Source/Drivers/PS1080/DDK/XnActualPropertiesHash.cpp b/Source/Drivers/PS1080/DDK/XnActualPropertiesHash.cpp new file mode 100644 index 0000000..f86febc --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualPropertiesHash.cpp @@ -0,0 +1,233 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "XnActualPropertiesHash.h" + +#include "XnActualIntProperty.h" +#include "XnActualRealProperty.h" +#include "XnActualStringProperty.h" +#include "XnActualGeneralProperty.h" +#include + +XnActualPropertiesHash::XnActualPropertiesHash(const XnChar* strName) +{ + strncpy(m_strName, strName, XN_DEVICE_MAX_STRING_LENGTH); +} + +XnActualPropertiesHash::~XnActualPropertiesHash() +{ + // free all properties + for (Iterator it = Begin(); it != End(); ++it) + { + XN_DELETE(it->Value()); + } +} + +XnStatus XnActualPropertiesHash::Add(XnUInt32 propertyId, const XnChar* strName, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + Iterator it = End(); + if (XN_STATUS_OK == Find(propertyId, it)) + { + return XN_STATUS_DEVICE_PROPERTY_ALREADY_EXISTS; + } + + // create property + XnActualIntProperty* pProp; + XN_VALIDATE_NEW(pProp, XnActualIntProperty, propertyId, strName, nValue, m_strName); + + // and add it to the hash + nRetVal = m_Hash.Set(propertyId, pProp); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pProp); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnActualPropertiesHash::Add(XnUInt32 propertyId, const XnChar* strName, XnDouble dValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + Iterator it = End(); + if (XN_STATUS_OK == Find(propertyId, it)) + { + return XN_STATUS_DEVICE_PROPERTY_ALREADY_EXISTS; + } + + // create property + XnActualRealProperty* pProp; + XN_VALIDATE_NEW(pProp, XnActualRealProperty, propertyId, strName, dValue, m_strName); + + // and add it to the hash + nRetVal = m_Hash.Set(propertyId, pProp); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pProp); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnActualPropertiesHash::Add(XnUInt32 propertyId, const XnChar* strName, const XnChar* strValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + Iterator it = End(); + if (XN_STATUS_OK == Find(propertyId, it)) + { + return XN_STATUS_DEVICE_PROPERTY_ALREADY_EXISTS; + } + + // create property + XnActualStringProperty* pProp; + XN_VALIDATE_NEW(pProp, XnActualStringProperty, propertyId, strName, strValue, m_strName); + + // and add it to the hash + nRetVal = m_Hash.Set(propertyId, pProp); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pProp); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnActualPropertiesHash::Add(XnUInt32 propertyId, const XnChar* strName, const OniGeneralBuffer& gbValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + Iterator it = End(); + if (XN_STATUS_OK == Find(propertyId, it)) + { + return XN_STATUS_DEVICE_PROPERTY_ALREADY_EXISTS; + } + + // create buffer + OniGeneralBuffer gbNew; + nRetVal = XnGeneralBufferAlloc(&gbNew, gbValue.dataSize); + XN_IS_STATUS_OK(nRetVal); + + // copy content + xnOSMemCopy(gbNew.data, gbValue.data, gbValue.dataSize); + + // create property + XnActualGeneralProperty* pProp; + XN_VALIDATE_NEW(pProp, XnActualGeneralProperty, propertyId, strName, gbNew, NULL, m_strName); + + pProp->SetAsBufferOwner(TRUE); + + // and add it to the hash + nRetVal = m_Hash.Set(propertyId, pProp); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pProp); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnActualPropertiesHash::Remove(XnUInt32 propertyId) +{ + XnStatus nRetVal = XN_STATUS_OK; + + ConstIterator it; + nRetVal = Find(propertyId, it); + XN_IS_STATUS_OK(nRetVal); + + return Remove(it); +} + +XnStatus XnActualPropertiesHash::Remove(ConstIterator where) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnProperty* pProp = where->Value(); + + nRetVal = m_Hash.Remove(where); + XN_IS_STATUS_OK(nRetVal); + + XN_DELETE(pProp); + + return (XN_STATUS_OK); +} + +XnStatus XnActualPropertiesHash::Clear() +{ + while (!IsEmpty()) + { + Remove(Begin()); + } + + return XN_STATUS_OK; +} + +XnStatus XnActualPropertiesHash::CopyFrom(const XnActualPropertiesHash& other) +{ + XnStatus nRetVal = XN_STATUS_OK; + + Clear(); + strncpy(m_strName, other.m_strName, XN_DEVICE_MAX_STRING_LENGTH); + + for (ConstIterator it = other.Begin(); it != other.End(); ++it) + { + switch (it->Value()->GetType()) + { + case XN_PROPERTY_TYPE_INTEGER: + { + XnActualIntProperty* pProp = (XnActualIntProperty*)it->Value(); + nRetVal = Add(pProp->GetId(), pProp->GetName(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_REAL: + { + XnActualRealProperty* pProp = (XnActualRealProperty*)it->Value(); + nRetVal = Add(pProp->GetId(), pProp->GetName(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_STRING: + { + XnActualStringProperty* pProp = (XnActualStringProperty*)it->Value(); + nRetVal = Add(pProp->GetId(), pProp->GetName(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_GENERAL: + { + XnActualGeneralProperty* pProp = (XnActualGeneralProperty*)it->Value(); + nRetVal = Add(pProp->GetId(), pProp->GetName(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Unknown property type: %d\n", it->Value()->GetType()); + } + } + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/DDK/XnActualPropertiesHash.h b/Source/Drivers/PS1080/DDK/XnActualPropertiesHash.h new file mode 100644 index 0000000..100f712 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualPropertiesHash.h @@ -0,0 +1,69 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ACTUAL_PROPERTIES_HASH_H__ +#define __XN_ACTUAL_PROPERTIES_HASH_H__ + +#include "XnProperty.h" +#include "XnActualIntProperty.h" +#include "XnActualRealProperty.h" +#include "XnActualStringProperty.h" +#include "XnActualGeneralProperty.h" + +/** +* A hash table of actual properties. The user can safely assume that every property in this +* hash is actual. +*/ +class XnActualPropertiesHash +{ +public: + XnActualPropertiesHash(const XnChar* strName); + ~XnActualPropertiesHash(); + + typedef XnPropertiesHash::Iterator Iterator; + typedef XnPropertiesHash::ConstIterator ConstIterator; + + XnStatus Add(XnUInt32 propertyId, const XnChar* strName, XnUInt64 nValue); + XnStatus Add(XnUInt32 propertyId, const XnChar* strName, XnDouble dValue); + XnStatus Add(XnUInt32 propertyId, const XnChar* strName, const XnChar* strValue); + XnStatus Add(XnUInt32 propertyId, const XnChar* strName, const OniGeneralBuffer& gbValue); + + XnStatus Remove(XnUInt32 propertyId); + XnStatus Remove(ConstIterator where); + inline XnBool IsEmpty() const { return m_Hash.IsEmpty(); } + XnStatus Clear(); + + inline XnStatus Find(XnUInt32 propertyId, Iterator& iter) { return m_Hash.Find(propertyId, iter); } + inline XnStatus Find(XnUInt32 propertyId, ConstIterator& iter) const { return m_Hash.Find(propertyId, iter); } + inline XnStatus Get(XnUInt32 propertyId, XnProperty*& pProp) const { return m_Hash.Get(propertyId, pProp); } + + inline Iterator Begin() { return m_Hash.Begin(); } + inline ConstIterator Begin() const { return m_Hash.Begin(); } + inline Iterator End() { return m_Hash.End(); } + inline ConstIterator End() const { return m_Hash.End(); } + + XnStatus CopyFrom(const XnActualPropertiesHash& other); + +protected: + XnPropertiesHash m_Hash; + XnChar m_strName[XN_DEVICE_MAX_STRING_LENGTH]; +}; + +#endif //__XN_ACTUAL_PROPERTIES_HASH_H__ diff --git a/Source/Drivers/PS1080/DDK/XnActualRealProperty.cpp b/Source/Drivers/PS1080/DDK/XnActualRealProperty.cpp new file mode 100644 index 0000000..23d9184 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualRealProperty.cpp @@ -0,0 +1,46 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnActualRealProperty.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnActualRealProperty::XnActualRealProperty(XnUInt32 propertyId, const XnChar* strName, XnDouble dInitialValue /* = 0.0 */, const XnChar* strModule /* = "" */) : + XnRealProperty(propertyId, strName, &m_dValue, strModule), + m_dValue(dInitialValue) +{ + // set a callback for get operations + UpdateGetCallback(GetCallback, this); +} + +XnStatus XnActualRealProperty::SetCallback(XnActualRealProperty* pSender, XnDouble dValue, void* /*pCookie*/) +{ + return pSender->UnsafeUpdateValue(dValue); +} + +XnStatus XN_CALLBACK_TYPE XnActualRealProperty::GetCallback(const XnActualRealProperty* pSender, XnDouble* pdValue, void* /*pCookie*/) +{ + *pdValue = pSender->GetValue(); + return XN_STATUS_OK; +} diff --git a/Source/Drivers/PS1080/DDK/XnActualRealProperty.h b/Source/Drivers/PS1080/DDK/XnActualRealProperty.h new file mode 100644 index 0000000..3413d7d --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualRealProperty.h @@ -0,0 +1,68 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ACTUAL_REAL_PROPERTY_H__ +#define __XN_ACTUAL_REAL_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Class +//--------------------------------------------------------------------------- + +/** +* A property of type integer. +*/ +class XnActualRealProperty : public XnRealProperty +{ +public: + XnActualRealProperty(XnUInt32 propertyId, const XnChar* strName, XnDouble dInitialValue = 0.0, const XnChar* strModule = ""); + + inline XnDouble GetValue() const { return m_dValue; } + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnActualRealProperty* pSender, XnDouble dValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnActualRealProperty* pSender, XnDouble* pdValue, void* pCookie); + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnRealProperty::UpdateSetCallback((XnRealProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateSetCallbackToDefault() + { + UpdateSetCallback(SetCallback, this); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnRealProperty::UpdateGetCallback((XnRealProperty::GetFuncPtr)pFunc, pCookie); + } + +private: + static XnStatus XN_CALLBACK_TYPE SetCallback(XnActualRealProperty* pSender, XnDouble dValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetCallback(const XnActualRealProperty* pSender, XnDouble* pdValue, void* pCookie); + + XnDouble m_dValue; +}; + +#endif //__XN_ACTUAL_REAL_PROPERTY_H__ diff --git a/Source/Drivers/PS1080/DDK/XnActualStringProperty.cpp b/Source/Drivers/PS1080/DDK/XnActualStringProperty.cpp new file mode 100644 index 0000000..4d04e66 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualStringProperty.cpp @@ -0,0 +1,46 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnActualStringProperty.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnActualStringProperty::XnActualStringProperty(XnUInt32 propertyId, const XnChar* strName, const XnChar* strInitialValue /* = "" */, const XnChar* strModule /* = "" */ ) : + XnStringProperty(propertyId, strName, m_strValue, strModule) +{ + strncpy(m_strValue, strInitialValue, XN_DEVICE_MAX_STRING_LENGTH); + // set a callback for get operations + UpdateGetCallback(GetCallback, this); +} + +XnStatus XnActualStringProperty::SetCallback(XnActualStringProperty* pSender, const XnChar* strValue, void* /*pCookie*/) +{ + return pSender->UnsafeUpdateValue(strValue); +} + +XnStatus XnActualStringProperty::GetCallback(const XnActualStringProperty* pSender, XnChar* csValue, void* /*pCookie*/) +{ + strncpy(csValue, pSender->GetValue(), XN_DEVICE_MAX_STRING_LENGTH); + return XN_STATUS_OK; +} diff --git a/Source/Drivers/PS1080/DDK/XnActualStringProperty.h b/Source/Drivers/PS1080/DDK/XnActualStringProperty.h new file mode 100644 index 0000000..6131ff1 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnActualStringProperty.h @@ -0,0 +1,68 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ACTUAL_STRING_PROPERTY_H__ +#define __XN_ACTUAL_STRING_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Class +//--------------------------------------------------------------------------- + +/** +* A property of type general. +*/ +class XnActualStringProperty : public XnStringProperty +{ +public: + XnActualStringProperty(XnUInt32 propertyId, const XnChar* strName, const XnChar* strInitialValue = "", const XnChar* strModule = ""); + + inline const XnChar* GetValue() const { return m_strValue; } + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnActualStringProperty* pSender, const XnChar* strValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnActualStringProperty* pSender, XnChar* csValue, void* pCookie); + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateSetCallback((XnProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateSetCallbackToDefault() + { + UpdateSetCallback(SetCallback, this); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateGetCallback((XnProperty::GetFuncPtr)pFunc, pCookie); + } + +private: + static XnStatus XN_CALLBACK_TYPE SetCallback(XnActualStringProperty* pSender, const XnChar* strValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetCallback(const XnActualStringProperty* pSender, XnChar* csValue, void* pCookie); + + XnChar m_strValue[XN_DEVICE_MAX_STRING_LENGTH]; +}; + +#endif //__XN_ACTUAL_STRING_PROPERTY_H__ diff --git a/Source/Drivers/PS1080/DDK/XnAudioStream.cpp b/Source/Drivers/PS1080/DDK/XnAudioStream.cpp new file mode 100644 index 0000000..1404d6f --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnAudioStream.cpp @@ -0,0 +1,103 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnAudioStream.h" +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_AUDIO_STREAM_BUFFER_SIZE_IN_SECONDS 1.5 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnAudioStream::XnAudioStream(const XnChar* csName, XnUInt32 nMaxNumberOfChannels) : + XnStreamingStream(XN_STREAM_TYPE_AUDIO, csName), + m_SampleRate(XN_STREAM_PROPERTY_SAMPLE_RATE, "SampleRate", XN_SAMPLE_RATE_48K), + m_NumberOfChannels(XN_STREAM_PROPERTY_NUMBER_OF_CHANNELS, "NumChannels", 2), + m_nMaxNumberOfChannels(nMaxNumberOfChannels) +{ +} + +XnStatus XnAudioStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnStreamingStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + m_SampleRate.UpdateSetCallback(SetSampleRateCallback, this); + m_NumberOfChannels.UpdateSetCallback(SetNumberOfChannelsCallback, this); + + XN_VALIDATE_ADD_PROPERTIES(this, &m_SampleRate, &m_NumberOfChannels); + + // required size + nRetVal = RegisterRequiredSizeProperty(&m_SampleRate); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnAudioStream::SetSampleRate(XnSampleRate nSampleRate) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_SampleRate.UnsafeUpdateValue(nSampleRate); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnAudioStream::SetNumberOfChannels(XnUInt32 nNumberOfChannels) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_NumberOfChannels.UnsafeUpdateValue(nNumberOfChannels); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnAudioStream::CalcRequiredSize(XnUInt32* pnRequiredSize) const +{ + XnUInt32 nSampleSize = 2 * m_nMaxNumberOfChannels; // 16-bit per channel (2 bytes) + XnUInt32 nSamples = (XnUInt32)(GetSampleRate() * XN_AUDIO_STREAM_BUFFER_SIZE_IN_SECONDS); + + *pnRequiredSize = nSamples * nSampleSize; + + return (XN_STATUS_OK); +} + +XnStatus XnAudioStream::SetSampleRateCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnAudioStream* pStream = (XnAudioStream*)pCookie; + return pStream->SetSampleRate((XnSampleRate)nValue); +} + +XnStatus XnAudioStream::SetNumberOfChannelsCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnAudioStream* pStream = (XnAudioStream*)pCookie; + return pStream->SetNumberOfChannels((XnUInt32)nValue); +} diff --git a/Source/Drivers/PS1080/DDK/XnAudioStream.h b/Source/Drivers/PS1080/DDK/XnAudioStream.h new file mode 100644 index 0000000..f9b7ab5 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnAudioStream.h @@ -0,0 +1,83 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_AUDIO_STREAM_H__ +#define __XN_AUDIO_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +/** +* Represents a default base implementation of an audio stream. +*/ +class XnAudioStream : public XnStreamingStream +{ +public: + XnAudioStream(const XnChar* csName, XnUInt32 nMaxNumberOfChannels); + ~XnAudioStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + inline XnSampleRate GetSampleRate() const { return (XnSampleRate)m_SampleRate.GetValue(); } + inline XnUInt32 GetNumberOfChannels() const { return (XnUInt32)m_NumberOfChannels.GetValue(); } + +protected: + //--------------------------------------------------------------------------- + // Properties Getters + //--------------------------------------------------------------------------- + inline XnActualIntProperty& SampleRateProperty() { return m_SampleRate; } + inline XnActualIntProperty& NumberOfChannelsProperty() { return m_NumberOfChannels; } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + virtual XnStatus SetSampleRate(XnSampleRate nSampleRate); + virtual XnStatus SetNumberOfChannels(XnUInt32 nNumberOfChannels); + + XnStatus CalcRequiredSize(XnUInt32* pnRequiredSize) const; + +private: + + static XnStatus XN_CALLBACK_TYPE SetSampleRateCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetNumberOfChannelsCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnActualIntProperty m_SampleRate; + XnActualIntProperty m_NumberOfChannels; + + XnUInt32 m_nMaxNumberOfChannels; +}; + +#endif //__XN_AUDIO_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnCodecFactory.cpp b/Source/Drivers/PS1080/DDK/XnCodecFactory.cpp new file mode 100644 index 0000000..36809d8 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnCodecFactory.cpp @@ -0,0 +1,113 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecFactory.h" +#include "XnIntProperty.h" +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnCodecFactory::Create(XnCompressionFormats nFormat, XnDeviceModule* pStream, const XnChar* /*StreamName*/, XnCodec** ppCodec) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnCodec* pCodec = NULL; + + switch (nFormat) + { + case XN_COMPRESSION_NONE: + { + XN_VALIDATE_NEW_AND_INIT(pCodec, XnUncompressedCodec); + } + break; + case XN_COMPRESSION_16Z: + { + XN_VALIDATE_NEW_AND_INIT(pCodec, Xn16zCodec); + } + break; + case XN_COMPRESSION_16Z_EMB_TABLE: + { + // first we need to find max depth + XnUInt64 nMaxDepth; + + nRetVal = pStream->GetProperty(XN_STREAM_PROPERTY_MAX_DEPTH, &nMaxDepth); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_NEW_AND_INIT(pCodec, Xn16zEmbTablesCodec, (OniDepthPixel)nMaxDepth); + } + break; + case XN_COMPRESSION_COLOR_8Z: + { + XN_VALIDATE_NEW_AND_INIT(pCodec, Xn8zCodec); + } + break; + case XN_COMPRESSION_JPEG: + { + // check what is the output format + XnUInt64 nOutputFormat; + nRetVal = pStream->GetProperty(XN_STREAM_PROPERTY_OUTPUT_FORMAT, &nOutputFormat); + XN_IS_STATUS_OK(nRetVal); + + XnBool bRGB = FALSE; + + switch (nOutputFormat) + { + case ONI_PIXEL_FORMAT_GRAY8: + bRGB = FALSE; + break; + case ONI_PIXEL_FORMAT_RGB888: + bRGB = TRUE; + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Codec factory currently supports JPEG codec only for streams of type Gray8 or RGB24!"); + } + + // take X and Y res + XnUInt64 nXRes, nYRes; + nRetVal = pStream->GetProperty(XN_STREAM_PROPERTY_X_RES, &nXRes); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pStream->GetProperty(XN_STREAM_PROPERTY_Y_RES, &nYRes); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_NEW_AND_INIT(pCodec, XnJpegCodec, bRGB, (XnUInt32)nXRes, (XnUInt32)nYRes); + } + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Codec factory does not support compression type %d", nFormat); + } + + *ppCodec = pCodec; + return (XN_STATUS_OK); +} + +void XnCodecFactory::Destroy(XnCodec* pCodec) +{ + XN_DELETE(pCodec); +} + diff --git a/Source/Drivers/PS1080/DDK/XnCodecFactory.h b/Source/Drivers/PS1080/DDK/XnCodecFactory.h new file mode 100644 index 0000000..3a19835 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnCodecFactory.h @@ -0,0 +1,42 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_CODEC_FACTORY_H__ +#define __XN_CODEC_FACTORY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnCodecFactory +{ +public: + static XnStatus Create(XnCompressionFormats nFormat, XnDeviceModule* pStream, const XnChar* StreamName, XnCodec** ppCodec); + static void Destroy(XnCodec* pCodec); +}; + +#endif //__XN_CODEC_FACTORY_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnDDK.cpp b/Source/Drivers/PS1080/DDK/XnDDK.cpp new file mode 100644 index 0000000..adfa682 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDDK.cpp @@ -0,0 +1,106 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnResolutions XnDDKGetResolutionFromXY(XnUInt32 nXRes, XnUInt32 nYRes) +{ + if (nXRes == 320 && nYRes == 240) return XN_RESOLUTION_QVGA; + if (nXRes == 640 && nYRes == 480) return XN_RESOLUTION_VGA; + if (nXRes == 1280 && nYRes == 1024) return XN_RESOLUTION_SXGA; + if (nXRes == 1600 && nYRes == 1200) return XN_RESOLUTION_UXGA; + if (nXRes == 160 && nYRes == 120) return XN_RESOLUTION_QQVGA; + if (nXRes == 176 && nYRes == 144) return XN_RESOLUTION_QCIF; + if (nXRes == 423 && nYRes == 240) return XN_RESOLUTION_240P; + if (nXRes == 352 && nYRes == 288) return XN_RESOLUTION_CIF; + if (nXRes == 640 && nYRes == 360) return XN_RESOLUTION_WVGA; + if (nXRes == 864 && nYRes == 480) return XN_RESOLUTION_480P; + if (nXRes == 800 && nYRes == 600) return XN_RESOLUTION_SVGA; + if (nXRes == 1024 && nYRes == 576) return XN_RESOLUTION_576P; + if (nXRes == 960 && nYRes == 720) return XN_RESOLUTION_DV; + if (nXRes == 1280 && nYRes == 720) return XN_RESOLUTION_720P; + // check if this is one of our special resolutions + if (nXRes == 800 && nYRes == 448) return XN_RESOLUTION_800_448; + if (nXRes == 1280 && nYRes == 960) return XN_RESOLUTION_1280_960; + return XN_RESOLUTION_CUSTOM; +} + +XnBool XnDDKGetXYFromResolution(XnResolutions res, XnUInt32* pnXRes, XnUInt32* pnYRes) +{ + switch(res) + { + case XN_RESOLUTION_QVGA: *pnXRes = 320; *pnYRes = 240; break; + case XN_RESOLUTION_VGA: *pnXRes = 640; *pnYRes = 480; break; + case XN_RESOLUTION_SXGA: *pnXRes = 1280; *pnYRes = 1024; break; + case XN_RESOLUTION_UXGA: *pnXRes = 1600; *pnYRes = 1200; break; + case XN_RESOLUTION_QQVGA: *pnXRes = 160; *pnYRes = 120; break; + case XN_RESOLUTION_QCIF: *pnXRes = 176; *pnYRes = 144; break; + case XN_RESOLUTION_240P: *pnXRes = 423; *pnYRes = 240; break; + case XN_RESOLUTION_CIF: *pnXRes = 352; *pnYRes = 288; break; + case XN_RESOLUTION_WVGA: *pnXRes = 640; *pnYRes = 360; break; + case XN_RESOLUTION_480P: *pnXRes = 864; *pnYRes = 480; break; + case XN_RESOLUTION_SVGA: *pnXRes = 800; *pnYRes = 600; break; + case XN_RESOLUTION_576P: *pnXRes = 1024; *pnYRes = 576; break; + case XN_RESOLUTION_DV: *pnXRes = 960; *pnYRes = 720; break; + case XN_RESOLUTION_720P: *pnXRes = 1280; *pnYRes = 720; break; + // check if this is one of our special resolutions + case XN_RESOLUTION_800_448: *pnXRes = 800; *pnYRes = 448; break; + case XN_RESOLUTION_1280_960: *pnXRes = 1280; *pnYRes = 960; break; + case XN_RESOLUTION_CUSTOM: return FALSE; + } + return TRUE; +} + +const XnChar* XnDDKGetResolutionName(XnResolutions res) +{ + switch (res) + { + case XN_RESOLUTION_QVGA: return "QVGA"; + case XN_RESOLUTION_VGA: return "VGA"; + case XN_RESOLUTION_SXGA: return "SXGA"; + case XN_RESOLUTION_UXGA: return "UXGA"; + case XN_RESOLUTION_QQVGA: return "QQVGA"; + case XN_RESOLUTION_QCIF: return "QCIF"; + case XN_RESOLUTION_240P: return "240P"; + case XN_RESOLUTION_CIF: return "CIF"; + case XN_RESOLUTION_WVGA: return "WVGA"; + case XN_RESOLUTION_480P: return "480P"; + case XN_RESOLUTION_SVGA: return "SVGA"; + case XN_RESOLUTION_576P: return "576P"; + case XN_RESOLUTION_DV: return "DV"; + case XN_RESOLUTION_720P: return "720P"; + // check if this is one of our special resolutions + case XN_RESOLUTION_CUSTOM: return "Custom"; + case XN_RESOLUTION_800_448: return "800x448"; + case XN_RESOLUTION_1280_960:return "1280x960"; + default: + XN_ASSERT(FALSE); + return "Custom"; + } +} diff --git a/Source/Drivers/PS1080/DDK/XnDDKStatus.cpp b/Source/Drivers/PS1080/DDK/XnDDKStatus.cpp new file mode 100644 index 0000000..276182c --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDDKStatus.cpp @@ -0,0 +1,26 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +// registration is done by including XnStatusRegister *before* including the list of errors +#include +#include diff --git a/Source/Drivers/PS1080/DDK/XnDepthStream.cpp b/Source/Drivers/PS1080/DDK/XnDepthStream.cpp new file mode 100644 index 0000000..53b4814 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDepthStream.cpp @@ -0,0 +1,133 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDepthStream.h" +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_DEPTH_STREAM_MAX_DEPTH_VALUE XN_MAX_UINT16 +#define XN_DEPTH_STREAM_DEFAULT_PIXEL_SIZE_FACTOR 1 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnDepthStream::XnDepthStream(const XnChar* csName, XnBool bAllowCustomResolutions, OniDepthPixel nDeviceMaxDepth, XnUInt16 nDeviceMaxShift) : + XnPixelStream(XN_STREAM_TYPE_DEPTH, csName, bAllowCustomResolutions), + m_MinDepth(XN_STREAM_PROPERTY_MIN_DEPTH, "MinDepth"), + m_MaxDepth(XN_STREAM_PROPERTY_MAX_DEPTH, "MaxDepth", nDeviceMaxDepth), + m_ConstShift(XN_STREAM_PROPERTY_CONST_SHIFT, "ConstShift"), + m_PixelSizeFactor(XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR, "PixelSizeFactor", 1), + m_MaxShift(XN_STREAM_PROPERTY_MAX_SHIFT, "MaxShift", nDeviceMaxShift), + m_DeviceMaxDepth(XN_STREAM_PROPERTY_DEVICE_MAX_DEPTH, "DeviceMaxDepth", nDeviceMaxDepth), + m_ParamCoefficient(XN_STREAM_PROPERTY_PARAM_COEFF, "ParamCoeff"), + m_ShiftScale(XN_STREAM_PROPERTY_SHIFT_SCALE, "ShiftScale"), + m_ZeroPlaneDistance(XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, "ZPD"), + m_ZeroPlanePixelSize(XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, "ZPPS"), + m_EmitterDCmosDistance(XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, "LDDIS"), + m_GetDCmosRCmosDistance(XN_STREAM_PROPERTY_DCMOS_RCMOS_DISTANCE, "DCRCDIS") +{ + m_MinDepth.UpdateSetCallback(SetMinDepthCallback, this); + m_MaxDepth.UpdateSetCallback(SetMaxDepthCallback, this); +} + +XnStatus XnDepthStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnPixelStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + // add properties + XN_VALIDATE_ADD_PROPERTIES(this, &m_MinDepth, &m_MaxDepth, &m_ConstShift, &m_PixelSizeFactor, + &m_MaxShift, &m_ParamCoefficient, &m_ShiftScale, &m_ZeroPlaneDistance, &m_ZeroPlanePixelSize, + &m_EmitterDCmosDistance, &m_GetDCmosRCmosDistance, &m_DeviceMaxDepth); + + nRetVal = OutputFormatProperty().UnsafeUpdateValue(ONI_PIXEL_FORMAT_DEPTH_1_MM); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_S2DHelper.Init(this); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDepthStream::Free() +{ + XnPixelStream::Free(); + return (XN_STATUS_OK); +} + +XnStatus XnDepthStream::SetMinDepth(OniDepthPixel nMinDepth) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (nMinDepth > GetDeviceMaxDepth()) + { + return XN_STATUS_DEVICE_BAD_PARAM; + } + + nRetVal = m_MinDepth.UnsafeUpdateValue(nMinDepth); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDepthStream::SetMaxDepth(OniDepthPixel nMaxDepth) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (nMaxDepth > GetDeviceMaxDepth()) + { + return XN_STATUS_DEVICE_BAD_PARAM; + } + + nRetVal = m_MaxDepth.UnsafeUpdateValue(nMaxDepth); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDepthStream::ValidateDepthValue(OniDepthPixel nDepth) +{ + if (nDepth > GetDeviceMaxDepth()) + { + return XN_STATUS_DEVICE_BAD_PARAM; + } + + return (XN_STATUS_OK); +} + +XnStatus XN_CALLBACK_TYPE XnDepthStream::SetMinDepthCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnDepthStream* pStream = (XnDepthStream*)pCookie; + return pStream->SetMinDepth((OniDepthPixel)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnDepthStream::SetMaxDepthCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnDepthStream* pStream = (XnDepthStream*)pCookie; + return pStream->SetMaxDepth((OniDepthPixel)nValue); +} diff --git a/Source/Drivers/PS1080/DDK/XnDepthStream.h b/Source/Drivers/PS1080/DDK/XnDepthStream.h new file mode 100644 index 0000000..34ff8bf --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDepthStream.h @@ -0,0 +1,119 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEPTH_STREAM_H__ +#define __XN_DEPTH_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnDepthStream : public XnPixelStream +{ +public: + XnDepthStream(const XnChar* csName, XnBool bAllowCustomResolutions, OniDepthPixel nDeviceMaxDepth, XnUInt16 nDeviceMaxShift); + ~XnDepthStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + XnStatus Free(); + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + inline OniDepthPixel GetMinDepth() const { return (OniDepthPixel)m_MinDepth.GetValue(); } + inline OniDepthPixel GetMaxDepth() const { return (OniDepthPixel)m_MaxDepth.GetValue(); } + inline XnUInt32 GetConstShift() const { return (XnUInt32)m_ConstShift.GetValue(); } + inline XnUInt32 GetPixelSizeFactor() const { return (XnUInt32)m_PixelSizeFactor.GetValue(); } + inline XnUInt16 GetMaxShift() const { return (XnUInt16)m_MaxShift.GetValue(); } + inline OniDepthPixel GetDeviceMaxDepth() const { return (OniDepthPixel)m_DeviceMaxDepth.GetValue(); } + inline XnUInt32 GetParamCoefficient() const { return (XnUInt32)m_ParamCoefficient.GetValue(); } + inline XnUInt32 GetShiftScale() const { return (XnUInt32)m_ShiftScale.GetValue(); } + inline OniDepthPixel GetZeroPlaneDistance() const { return (OniDepthPixel)m_ZeroPlaneDistance.GetValue(); } + inline XnDouble GetZeroPlanePixelSize() const { return m_ZeroPlanePixelSize.GetValue(); } + inline XnDouble GetEmitterDCmosDistance() const { return m_EmitterDCmosDistance.GetValue(); } + inline XnDouble GetDCmosRCmosDistance() const { return m_GetDCmosRCmosDistance.GetValue(); } + + inline OniDepthPixel* GetShiftToDepthTable() const { return m_S2DHelper.GetShiftToDepthTable(); } + inline XnUInt16* GetDepthToShiftTable() const { return m_S2DHelper.GetDepthToShiftTable(); } + +protected: + //--------------------------------------------------------------------------- + // Properties Getters + //--------------------------------------------------------------------------- + inline XnActualIntProperty& MinDepthProperty() { return m_MinDepth; } + inline XnActualIntProperty& MaxDepthProperty() { return m_MaxDepth; } + inline XnActualIntProperty& ConstShiftProperty() { return m_ConstShift; } + inline XnActualIntProperty& PixelSizeFactorProperty() { return m_PixelSizeFactor; } + inline XnActualIntProperty& MaxShiftProperty() { return m_MaxShift; } + inline XnActualIntProperty& DeviceMaxDepthProperty() { return m_DeviceMaxDepth; } + inline XnActualIntProperty& ParamCoefficientProperty() { return m_ParamCoefficient; } + inline XnActualIntProperty& ShiftScaleProperty() { return m_ShiftScale; } + inline XnActualIntProperty& ZeroPlaneDistanceProperty() { return m_ZeroPlaneDistance; } + inline XnActualRealProperty& ZeroPlanePixelSizeProperty() { return m_ZeroPlanePixelSize; } + inline XnActualRealProperty& EmitterDCmosDistanceProperty() { return m_EmitterDCmosDistance; } + inline XnActualRealProperty& GetDCmosRCmosDistanceProperty() { return m_GetDCmosRCmosDistance; } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + virtual XnStatus SetMinDepth(OniDepthPixel nMinDepth); + virtual XnStatus SetMaxDepth(OniDepthPixel nMaxDepth); + +protected: + //--------------------------------------------------------------------------- + // Helper functions + //--------------------------------------------------------------------------- + XnStatus ValidateDepthValue(OniDepthPixel nDepth); + +private: + // callbacks + static XnStatus XN_CALLBACK_TYPE SetMinDepthCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetMaxDepthCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnActualIntProperty m_MinDepth; + XnActualIntProperty m_MaxDepth; + XnActualIntProperty m_ConstShift; + XnActualIntProperty m_PixelSizeFactor; + XnActualIntProperty m_MaxShift; + XnActualIntProperty m_DeviceMaxDepth; + XnActualIntProperty m_ParamCoefficient; + XnActualIntProperty m_ShiftScale; + XnActualIntProperty m_ZeroPlaneDistance; + XnActualRealProperty m_ZeroPlanePixelSize; + XnActualRealProperty m_EmitterDCmosDistance; + XnActualRealProperty m_GetDCmosRCmosDistance; + + XnShiftToDepthStreamHelper m_S2DHelper; +}; + +#endif //__XN_DEPTH_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnDeviceBase.cpp b/Source/Drivers/PS1080/DDK/XnDeviceBase.cpp new file mode 100644 index 0000000..45f4d4e --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceBase.cpp @@ -0,0 +1,1152 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceBase.h" +#include +#include +#include "XnIntProperty.h" +#include "XnRealProperty.h" +#include "XnStringProperty.h" +#include "XnGeneralProperty.h" +#include "XnPropertySetInternal.h" +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_DUMP_STREAMS_DATA "StreamsData" + +//--------------------------------------------------------------------------- +// Public Methods +//--------------------------------------------------------------------------- +XnDeviceBase::XnDeviceBase() : + m_pDevicePropertiesHolder(NULL), + m_DeviceMirror(XN_MODULE_PROPERTY_MIRROR, "Mirror", TRUE), + m_StreamsDataDump(NULL), + m_hLock(NULL) +{ + // update set callbacks + m_DeviceMirror.UpdateSetCallback(SetMirrorCallback, this); +} + +XnDeviceBase::~XnDeviceBase() +{ +} + +XnStatus XnDeviceBase::Init(const XnDeviceConfig* pDeviceConfig) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSCreateCriticalSection(&m_hLock); + XN_IS_STATUS_OK(nRetVal); + + // first init the device + nRetVal = InitImpl(pDeviceConfig); + XN_IS_STATUS_OK(nRetVal); + + // and now create streams + if (pDeviceConfig->pInitialValues != NULL) + { + nRetVal = CreateStreams(pDeviceConfig->pInitialValues); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::InitImpl(const XnDeviceConfig* pDeviceConfig) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pDeviceConfig); + + // create device module + nRetVal = CreateDeviceModule(&m_pDevicePropertiesHolder); + XN_IS_STATUS_OK(nRetVal); + + // check if we have initial values for device modules + XnActualPropertiesHash* pDeviceModuleInitialProps = NULL; + if (pDeviceConfig->pInitialValues != NULL) + { + pDeviceConfig->pInitialValues->pData->Get(XN_MODULE_NAME_DEVICE, pDeviceModuleInitialProps); + } + + // init device module + nRetVal = m_pDevicePropertiesHolder->Init(pDeviceModuleInitialProps); + XN_IS_STATUS_OK(nRetVal); + + // add the device module + nRetVal = AddModule(m_pDevicePropertiesHolder); + XN_IS_STATUS_OK(nRetVal); + + // init dump + m_StreamsDataDump = xnDumpFileOpen(XN_DUMP_STREAMS_DATA, "%s.csv", XN_DUMP_STREAMS_DATA); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::Destroy() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // free all modules + while (m_Modules.Size() != 0) + { + XnDeviceModuleHolder* pModuleHolder = m_Modules.Begin()->Value(); + if (IsStream(pModuleHolder->GetModule())) + { + XnChar strName[XN_DEVICE_MAX_STRING_LENGTH]; + strcpy(strName, pModuleHolder->GetModule()->GetName()); + nRetVal = DestroyStream(strName); + XN_IS_STATUS_OK(nRetVal); + } + else + { + // free memory of registered properties to this module + FreeModuleRegisteredProperties(m_Modules.Begin()->Key()); + + pModuleHolder->GetModule()->Free(); + DestroyModule(pModuleHolder); + m_Modules.Remove(m_Modules.Begin()); + } + } + + m_pDevicePropertiesHolder = NULL; + + m_Modules.Clear(); + + // close dump + xnDumpFileClose(m_StreamsDataDump); + + if (m_hLock != NULL) + { + xnOSCloseCriticalSection(&m_hLock); + m_hLock = NULL; + } + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::CreateModule(const XnChar* strName, XnDeviceModuleHolder** ppModuleHolder) +{ + XnDeviceModule* pModule; + XnDeviceModuleHolder* pHolder; + + // create module + XN_VALIDATE_NEW(pModule, XnDeviceModule, strName); + + // create holder + pHolder = XN_NEW(XnDeviceModuleHolder, pModule); + if (pHolder == NULL) + { + XN_DELETE(pModule); + return XN_STATUS_ALLOC_FAILED; + } + + *ppModuleHolder = pHolder; + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::CreateDeviceModule(XnDeviceModuleHolder** ppModuleHolder) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // create module + nRetVal = CreateModule(XN_MODULE_NAME_DEVICE, ppModuleHolder); + XN_IS_STATUS_OK(nRetVal); + + XnDeviceModule* pModule = (*ppModuleHolder)->GetModule(); + + // add device properties + XnProperty* pProps[] = { &m_DeviceMirror }; + + nRetVal = pModule->AddProperties(pProps, sizeof(pProps)/sizeof(XnProperty*)); + if (nRetVal != XN_STATUS_OK) + { + DestroyModule(*ppModuleHolder); + *ppModuleHolder = NULL; + return (nRetVal); + } + + return XN_STATUS_OK; +} + +void XnDeviceBase::DestroyModule(XnDeviceModuleHolder* pModuleHolder) +{ + XN_DELETE(pModuleHolder->GetModule()); + XN_DELETE(pModuleHolder); +} + +XnStatus XnDeviceBase::SetMirror(XnBool bMirror) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // change all streams + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + XnDeviceModuleHolder* pModuleHolder = it->Value(); + if (IsStream(pModuleHolder->GetModule())) + { + XnDeviceStream* pStream = (XnDeviceStream*)pModuleHolder->GetModule(); + nRetVal = pStream->SetMirror(bMirror); + XN_IS_STATUS_OK(nRetVal); + } + } + + // and set property + nRetVal = m_DeviceMirror.UnsafeUpdateValue(bMirror); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::GetSupportedStreams(const XnChar** aStreamNames, XnUInt32* pnStreamNamesCount) +{ + XN_VALIDATE_OUTPUT_PTR(pnStreamNamesCount); + // NOTE: we allow aStreamName to be NULL + + // first of all count streams + XnUInt32 nStreamsCount = m_SupportedStreams.Size(); + + // now check if we have enough room + if (nStreamsCount > *pnStreamNamesCount) + { + *pnStreamNamesCount = nStreamsCount; + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + // now copy values + nStreamsCount = 0; + for (XnStringsSet::Iterator it = m_SupportedStreams.Begin(); it != m_SupportedStreams.End(); ++it) + { + aStreamNames[nStreamsCount] = it->Key(); + nStreamsCount++; + } + + *pnStreamNamesCount = nStreamsCount; + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::OpenStream(const XnChar* StreamName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(StreamName); + + xnLogVerbose(XN_MASK_DDK, "Opening stream %s...", StreamName); + + // find this stream + XnDeviceStream* pStream; + nRetVal = FindStream(StreamName, &pStream); + XN_IS_STATUS_OK(nRetVal); + + // open it + nRetVal = pStream->Open(); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_DDK, "Stream %s is open.", StreamName); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::CloseStream(const XnChar* StreamName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(StreamName); + + xnLogVerbose(XN_MASK_DDK, "Closing stream %s...", StreamName); + + // find this stream + XnDeviceStream* pStream; + nRetVal = FindStream(StreamName, &pStream); + XN_IS_STATUS_OK(nRetVal); + + // close it + nRetVal = pStream->Close(); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_DDK, "Stream %s is closed.", StreamName); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::OpenAllStreams() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_DDK, "Opening all streams..."); + + // go over modules list, and look for closed streams + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + XnDeviceModuleHolder* pModuleHolder = it->Value(); + if (IsStream(pModuleHolder->GetModule())) + { + XnDeviceStream* pStream = (XnDeviceStream*)pModuleHolder->GetModule(); + if (!pStream->IsOpen()) + { + nRetVal = pStream->Open(); + XN_IS_STATUS_OK(nRetVal); + } + } + } + + xnLogInfo(XN_MASK_DDK, "All streams are open."); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::CloseAllStreams() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_DDK, "Closing all streams..."); + + // go over modules list, and look for closed streams + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + XnDeviceModuleHolder* pModuleHolder = it->Value(); + if (IsStream(pModuleHolder->GetModule())) + { + XnDeviceStream* pStream = (XnDeviceStream*)pModuleHolder->GetModule(); + if (pStream->IsOpen()) + { + nRetVal = pStream->Close(); + XN_IS_STATUS_OK(nRetVal); + } + } + } + + xnLogInfo(XN_MASK_DDK, "All streams are closed."); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::GetStreamNames(const XnChar** pstrNames, XnUInt32* pnNamesCount) +{ + // first we need to count them + XnUInt32 nCount = 0; + + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + XnDeviceModuleHolder* pModuleHolder = it->Value(); + if (IsStream(pModuleHolder->GetModule())) + { + nCount++; + } + } + + if (nCount > *pnNamesCount) + { + *pnNamesCount = nCount; + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + // OK. we have enough space. Copy into it + nCount = 0; + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + XnDeviceModuleHolder* pModuleHolder = it->Value(); + if (IsStream(pModuleHolder->GetModule())) + { + pstrNames[nCount] = it->Key(); + nCount++; + } + } + + *pnNamesCount = nCount; + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::DoesModuleExist(const XnChar* ModuleName, XnBool* pbDoesExist) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(ModuleName); + XN_VALIDATE_OUTPUT_PTR(pbDoesExist); + + *pbDoesExist = FALSE; + + XnDeviceModuleHolder* pModuleHolder; + nRetVal = FindModule(ModuleName, &pModuleHolder); + if (nRetVal == XN_STATUS_OK) + { + *pbDoesExist = TRUE; + } + else if (nRetVal != XN_STATUS_DEVICE_MODULE_NOT_FOUND) + { + return nRetVal; + } + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::RegisterToNewStreamData(XnDeviceOnNewStreamDataEventHandler Handler, void* pCookie, XnCallbackHandle& hCallback) +{ + XN_VALIDATE_INPUT_PTR(Handler); + + return m_OnNewStreamDataEvent.Register(Handler, pCookie, hCallback); +} + +XnStatus XnDeviceBase::UnregisterFromNewStreamData(XnCallbackHandle hCallback) +{ + XN_VALIDATE_INPUT_PTR(hCallback); + + return m_OnNewStreamDataEvent.Unregister(hCallback); +} + +XnStatus XnDeviceBase::DoesPropertyExist(const XnChar* ModuleName, XnUInt32 propertyId, XnBool* pbDoesExist) +{ + XnStatus nRetVal = XN_STATUS_OK; + + *pbDoesExist = FALSE; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + if (nRetVal == XN_STATUS_DEVICE_MODULE_NOT_FOUND) + { + return XN_STATUS_OK; + } + else + { + XN_IS_STATUS_OK(nRetVal); + } + + nRetVal = pModule->DoesPropertyExist(propertyId, pbDoesExist); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::GetPropertyType(const XnChar* ModuleName, XnUInt32 propertyId, XnPropertyType* pnType) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->GetPropertyType(propertyId, pnType); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->SetProperty(propertyId, nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnDouble dValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->SetProperty(propertyId, dValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, const XnChar* csValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->SetProperty(propertyId, csValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, const OniGeneralBuffer& gbValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->SetProperty(propertyId, gbValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnUInt64* pnValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->GetProperty(propertyId, pnValue); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnDouble* pdValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->GetProperty(propertyId, pdValue); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnChar* csValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->GetProperty(propertyId, csValue); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, const OniGeneralBuffer& gbValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(ModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->GetProperty(propertyId, gbValue); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::LoadConfigFromFile(const XnChar* csINIFilePath, const XnChar* csSectionName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = DeviceModule()->LoadConfigFromFile(csINIFilePath, csSectionName); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::BatchConfig(const XnPropertySet* pChangeSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pChangeSet); + + for (XnPropertySetData::ConstIterator itModule = pChangeSet->pData->Begin(); itModule != pChangeSet->pData->End(); ++itModule) + { + // find this module + XnDeviceModule* pModule = NULL; + nRetVal = FindModule(itModule->Key(), &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->BatchConfig(*itModule->Value()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::GetAllProperties(XnPropertySet* pSet, XnBool bNoStreams /* = FALSE */, const XnChar* strModule /* = NULL */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + + // clear the set + nRetVal = XnPropertySetClear(pSet); + XN_IS_STATUS_OK(nRetVal); + + if (strModule != NULL) + { + XnDeviceModule* pModule; + nRetVal = FindModule(strModule, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->GetAllProperties(pSet); + XN_IS_STATUS_OK(nRetVal); + } + else + { + // enumerate over modules + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + XnDeviceModuleHolder* pModuleHolder = it->Value(); + + if (bNoStreams && IsStream(pModuleHolder->GetModule())) + continue; + + nRetVal = pModuleHolder->GetModule()->GetAllProperties(pSet); + XN_IS_STATUS_OK(nRetVal); + } + } + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::RegisterToPropertyChange(const XnChar* Module, XnUInt32 propertyId, XnDeviceOnPropertyChangedEventHandler Handler, void* pCookie, XnCallbackHandle& hCallback) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(Module, &pModule); + XN_IS_STATUS_OK(nRetVal); + + XnPropertyCallback* pRealCookie = NULL; + XN_VALIDATE_NEW(pRealCookie, XnPropertyCallback, Module, propertyId, Handler, pCookie); + + // register + nRetVal = pModule->RegisterForOnPropertyValueChanged(propertyId, PropertyValueChangedCallback, pRealCookie, pRealCookie->hCallback); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pRealCookie); + return (nRetVal); + } + + m_PropertyCallbacks.AddLast(pRealCookie); + + hCallback = (XnCallbackHandle) pRealCookie; + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::UnregisterFromPropertyChange(const XnChar* Module, XnUInt32 propertyId, XnCallbackHandle hCallback) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(Module); + XN_VALIDATE_INPUT_PTR(hCallback); + + XnPropertyCallback* pRealCookie = (XnPropertyCallback*)hCallback; + + XnDeviceModule* pModule; + nRetVal = FindModule(Module, &pModule); + XN_IS_STATUS_OK(nRetVal); + + // first unregister it from property + nRetVal = pModule->UnregisterFromOnPropertyValueChanged(propertyId, pRealCookie->hCallback); + XN_IS_STATUS_OK(nRetVal); + + PropertiesCallbacks::Iterator it = m_PropertyCallbacks.Find(pRealCookie); + if (it != m_PropertyCallbacks.End()) + { + m_PropertyCallbacks.Remove(it); + } + + // now free the memory + XN_DELETE(pRealCookie); + + return (XN_STATUS_OK); +} + +//--------------------------------------------------------------------------- +// Protected Helper Methods +//--------------------------------------------------------------------------- + +XnStatus XnDeviceBase::AddModule(XnDeviceModuleHolder* pModuleHolder) +{ + XnDeviceModule* pModule = pModuleHolder->GetModule(); + + // make sure module doesn't exist yet + if (m_Modules.Find(pModule->GetName()) != m_Modules.End()) + { + xnLogError(XN_MASK_DEVICE, "A module with the name %s already exists!", pModule->GetName()); + return XN_STATUS_ERROR; + } + + // add it to the list + XnStatus nRetVal = m_Modules.Set(pModule->GetName(), pModuleHolder); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::RemoveModule(const XnChar* ModuleName) +{ + // remove it + XnStatus nRetVal = m_Modules.Remove(ModuleName); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::FindModule(const XnChar* ModuleName, XnDeviceModule** ppModule) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModuleHolder* pHolder; + nRetVal = FindModule(ModuleName, &pHolder); + XN_IS_STATUS_OK(nRetVal); + + *ppModule = pHolder->GetModule(); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::FindModule(const XnChar* ModuleName, XnDeviceModuleHolder** ppModuleHolder) +{ + XnStatus nRetVal = XN_STATUS_OK; + ModuleHoldersHash::Iterator it = m_Modules.Find(ModuleName); + if (it == m_Modules.End()) + { + return (XN_STATUS_DEVICE_MODULE_NOT_FOUND); + } + XN_IS_STATUS_OK(nRetVal); + + *ppModuleHolder = it->Value(); + + return XN_STATUS_OK; +} + +XnBool XnDeviceBase::IsStream(XnDeviceModule* pModule) +{ + XnProperty* pProperty; + XnStatus nRetVal = pModule->GetProperty(XN_STREAM_PROPERTY_IS_STREAM, &pProperty); + if (nRetVal != XN_STATUS_OK) + return FALSE; + + if (pProperty->GetType() != XN_PROPERTY_TYPE_INTEGER) + return FALSE; + + XnIntProperty* pIntProperty = (XnIntProperty*)pProperty; + + XnUInt64 nValue; + nRetVal = pIntProperty->GetValue(&nValue); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_DDK, "Failed getting the value of the IsStream property: %s", xnGetStatusString(nRetVal)); + return FALSE; + } + + return (XnBool)nValue; +} + +XnStatus XnDeviceBase::FindStream(const XnChar* StreamName, XnDeviceStream** ppStream) +{ + // find the module + XnDeviceModuleHolder* pStreamHolder = NULL; + XnStatus nRetVal = FindStream(StreamName, &pStreamHolder); + XN_IS_STATUS_OK(nRetVal); + + *ppStream = (XnDeviceStream*)pStreamHolder->GetModule(); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::FindStream(const XnChar* StreamName, XnDeviceModuleHolder** ppStreamHolder) +{ + // find the module + XnDeviceModuleHolder* pModuleHolder = NULL; + XnStatus nRetVal = FindModule(StreamName, &pModuleHolder); + XN_IS_STATUS_OK(nRetVal); + + // check if this is a stream + if (!IsStream(pModuleHolder->GetModule())) + return XN_STATUS_MODULE_IS_NOT_STREAM; + + *ppStreamHolder = pModuleHolder; + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::AddSupportedStream(const XnChar* StreamType) +{ + // make sure stream doesn't exist yet + XnStringsSet::Iterator it = m_SupportedStreams.End(); + if (XN_STATUS_OK == m_SupportedStreams.Find(StreamType, it)) + { + xnLogError(XN_MASK_DEVICE, "A stream with the name %s already exists!", StreamType); + return XN_STATUS_STREAM_ALREADY_EXISTS; + } + + // add it to the list + XnStatus nRetVal = m_SupportedStreams.Set(StreamType); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::GetStreamRequiredDataSize(const XnChar* StreamName, XnUInt32* pnRequiredSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // find stream + XnDeviceStream* pStream; + nRetVal = FindStream(StreamName, &pStream); + XN_IS_STATUS_OK(nRetVal); + + *pnRequiredSize = pStream->GetRequiredDataSize(); + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::CreateStreams(const XnPropertySet* pSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + for (XnPropertySetData::ConstIterator it = pSet->pData->Begin(); it != pSet->pData->End(); ++it) + { + // check if this module is a stream + XnActualPropertiesHash* pModule = it->Value(); + + XnActualPropertiesHash::ConstIterator itProp = pModule->End(); + if (XN_STATUS_OK == pModule->Find(XN_STREAM_PROPERTY_TYPE, itProp)) + { + // create a copy of the properties + XnActualPropertiesHash streamProps(it->Key()); + nRetVal = streamProps.CopyFrom(*pModule); + XN_IS_STATUS_OK(nRetVal); + + // remove the type property + nRetVal = streamProps.Remove(XN_STREAM_PROPERTY_TYPE); + XN_IS_STATUS_OK(nRetVal); + + // and create the stream + XnActualStringProperty* pActualProp = (XnActualStringProperty*)itProp->Value(); + nRetVal = CreateStreamImpl(pActualProp->GetValue(), it->Key(), &streamProps); + XN_IS_STATUS_OK(nRetVal); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::ValidateOnlyModule(const XnPropertySet* pSet, const XnChar* StreamName) +{ + XnPropertySetData::ConstIterator it = pSet->pData->Begin(); + if (it == pSet->pData->End()) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DDK, "Property set did not contain any stream!"); + } + + if (strcmp(it->Key(), StreamName) != 0) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DDK, "Property set module name does not match stream name!"); + } + + if (++it != pSet->pData->End()) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DDK, "Property set contains more than one module!"); + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::CreateStream(const XnChar* StreamType, const XnChar* StreamName /* = NULL */, const XnPropertySet* pInitialValues /* = NULL */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // check for name + if (StreamName == NULL) + StreamName = StreamType; + + XnActualPropertiesHash* pInitialValuesHash = NULL; + + if (pInitialValues != NULL) + { + // validate property set + nRetVal = ValidateOnlyModule(pInitialValues, StreamName); + XN_IS_STATUS_OK(nRetVal); + + pInitialValuesHash = pInitialValues->pData->Begin()->Value(); + } + + nRetVal = CreateStreamImpl(StreamType, StreamName, pInitialValuesHash); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::CreateStreamImpl(const XnChar* strType, const XnChar* strName, const XnActualPropertiesHash* pInitialSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogInfo(XN_MASK_DDK, "Creating stream '%s' of type '%s'...", strName, strType); + + xnl::AutoCSLocker lock(m_hLock); + + XnDeviceModule* pModule; + if (FindModule(strName, &pModule) == XN_STATUS_OK) + { + // already exists. check sharing mode (when shared, we allow "creating" the same stream) + if (!IsStream(pModule) || + strcmp(strType, ((XnDeviceStream*)pModule)->GetType()) != 0) + { + XN_LOG_WARNING_RETURN(XN_STATUS_STREAM_ALREADY_EXISTS, XN_MASK_DDK, "A stream with this name already exists!"); + } + + // OK, we'll allow this. Just set new configuration + if (pInitialSet != NULL) + { + nRetVal = pModule->BatchConfig(*pInitialSet); + XN_IS_STATUS_OK(nRetVal); + } + + ((XnDeviceStream*)pModule)->AddRef(); + } + else + { + // create stream + XnDeviceModuleHolder* pNewStreamHolder = NULL; + + nRetVal = CreateStreamModule(strType, strName, &pNewStreamHolder); + XN_IS_STATUS_OK(nRetVal); + + XnDeviceStream* pNewStream = (XnDeviceStream*)(pNewStreamHolder->GetModule()); + if (pNewStream == NULL) + { + DestroyStreamModule(pNewStreamHolder); + XN_LOG_ERROR_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Internal Error: Invalid new stream!"); + } + + // initialize the stream + xnLogVerbose(XN_MASK_DDK, "Initializing stream '%s'...", strName); + + nRetVal = pNewStreamHolder->Init(pInitialSet); + if (nRetVal != XN_STATUS_OK) + { + DestroyStreamModule(pNewStreamHolder); + return (nRetVal); + } + + // set it's mirror value (if not requested otherwise) + XnBool bSetMirror = TRUE; + + if (pInitialSet != NULL) + { + XnActualPropertiesHash::ConstIterator it = pInitialSet->End(); + if (XN_STATUS_OK == pInitialSet->Find(XN_MODULE_PROPERTY_MIRROR, it)) + { + bSetMirror = FALSE; + } + } + + if (bSetMirror) + { + nRetVal = pNewStream->SetMirror((XnBool)m_DeviceMirror.GetValue()); + if (nRetVal != XN_STATUS_OK) + { + DestroyStreamModule(pNewStreamHolder); + return (nRetVal); + } + } + + // add it to the list of existing modules + nRetVal = AddModule(pNewStreamHolder); + if (nRetVal != XN_STATUS_OK) + { + DestroyStreamModule(pNewStreamHolder); + return (nRetVal); + } + + xnLogInfo(XN_MASK_DDK, "Stream '%s' was initialized.", strName); + + pNewStream->SetNewDataCallback(NewStreamDataCallback, this); + + xnLogInfo(XN_MASK_DDK, "'%s' stream was created.", strName); + } + + return (XN_STATUS_OK); +} + +void XnDeviceBase::FreeModuleRegisteredProperties(const XnChar* strModule) +{ + // free memory of registered properties to this stream + PropertiesCallbacks::Iterator it = m_PropertyCallbacks.Begin(); + while (it != m_PropertyCallbacks.End()) + { + PropertiesCallbacks::Iterator cur = it; + it++; + + XnPropertyCallback* pRealCallback = *cur; + if (strcmp(pRealCallback->strModule, strModule) == 0) + { + m_PropertyCallbacks.Remove(cur); + XN_DELETE(pRealCallback); + } + } +} + +XnStatus XnDeviceBase::DestroyStream(const XnChar* StreamName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogInfo(XN_MASK_DDK, "Destroying stream '%s'...", StreamName); + + // keep the stream name (we now delete the module, so the name will be lost) + XnChar strStreamName[XN_DEVICE_MAX_STRING_LENGTH]; + strncpy(strStreamName, StreamName, XN_DEVICE_MAX_STRING_LENGTH); + + xnl::AutoCSLocker lock(m_hLock); + + // Find the stream + XnDeviceModuleHolder* pStreamHolder; + nRetVal = FindStream(strStreamName, &pStreamHolder); + XN_IS_STATUS_OK(nRetVal); + + XnDeviceStream* pStream = (XnDeviceStream*)pStreamHolder->GetModule(); + XnUInt32 nRefCount = pStream->DecRef(); + if (0 == nRefCount) + { + // remove it from map + nRetVal = RemoveModule(strStreamName); + XN_IS_STATUS_OK(nRetVal); + + // and free it's memory + DestroyStreamModule(pStreamHolder); + + // free memory of registered properties to this stream + FreeModuleRegisteredProperties(StreamName); + + xnLogVerbose(XN_MASK_DDK, "'%s' stream destroyed.", strStreamName); + } + else + { + xnLogVerbose(XN_MASK_DDK, "'%s' stream now has %d references.", strStreamName, nRefCount); + } + + return XN_STATUS_OK; +} + +XnStatus XnDeviceBase::GetModulesList(XnDeviceModuleHolder** apModules, XnUInt32* pnCount) +{ + XnUInt32 nCount = 0; + + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + apModules[nCount] = it->Value(); + nCount++; + } + + *pnCount = nCount; + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::GetModulesList(XnDeviceModuleHolderList& list) +{ + list.Clear(); + + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + list.AddLast(it->Value()); + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::GetStreamsList(XnDeviceModuleHolderList& list) +{ + list.Clear(); + + for (ModuleHoldersHash::Iterator it = m_Modules.Begin(); it != m_Modules.End(); ++it) + { + XnDeviceModuleHolder* pModuleHolder = it->Value(); + if (IsStream(pModuleHolder->GetModule())) + { + list.AddLast(pModuleHolder); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceBase::RaiseNewStreamDataEvent(const XnChar* StreamName, OniFrame* pFrame) +{ + XnNewStreamDataEventArgs eventArgs; + eventArgs.strStreamName = StreamName; + eventArgs.pFrame = pFrame; + + m_OnNewStreamDataEvent.Raise(eventArgs); + + return XN_STATUS_OK; +} + +void XnDeviceBase::OnNewStreamData(XnDeviceStream* pStream, OniFrame* pFrame) +{ + XnUInt64 nNow; + xnOSGetHighResTimeStamp(&nNow); + xnDumpFileWriteString(m_StreamsDataDump, "%llu,%s,%llu,%u\n", nNow, pStream->GetName(), pFrame->timestamp, pFrame->frameIndex); + + RaiseNewStreamDataEvent(pStream->GetName(), pFrame); +} + +void XnDeviceBase::NewStreamDataCallback(XnDeviceStream* pSender, OniFrame* pFrame, void* pCookie) +{ + XnDeviceBase* pThis = (XnDeviceBase*)pCookie; + pThis->OnNewStreamData(pSender, pFrame); +} + +XnStatus XN_CALLBACK_TYPE XnDeviceBase::PropertyValueChangedCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnPropertyCallback* pUserCallback = (XnPropertyCallback*)pCookie; + + // TODO: consider catching exceptions (if user does some stupid things) + pUserCallback->pHandler(pUserCallback->strModule, pUserCallback->propertyId, pUserCallback->pCookie); + + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnDeviceBase::SetMirrorCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnDeviceBase* pThis = (XnDeviceBase*)pCookie; + return pThis->SetMirror((XnBool)nValue); +} + +XnDeviceBase::XnPropertyCallback::XnPropertyCallback(const XnChar* strModule, XnUInt32 propertyId, XnDeviceOnPropertyChangedEventHandler pHandler, void* pCookie) : + propertyId(propertyId), + pCookie(pCookie), + pHandler(pHandler) +{ + strcpy(this->strModule, strModule); +} diff --git a/Source/Drivers/PS1080/DDK/XnDeviceBase.h b/Source/Drivers/PS1080/DDK/XnDeviceBase.h new file mode 100644 index 0000000..e0aed0a --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceBase.h @@ -0,0 +1,232 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEVICE_BASE_H__ +#define __XN_DEVICE_BASE_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include "XnDeviceModuleHolder.h" +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_MASK_DEVICE "Device" +#define XN_DEVICE_BASE_MAX_STREAMS_COUNT 100 + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +class XnDeviceBase +{ +public: + XnDeviceBase(); + virtual ~XnDeviceBase(); + + //--------------------------------------------------------------------------- + // Properties Getters + //--------------------------------------------------------------------------- + inline XnActualIntProperty& DeviceMirrorProperty() { return m_DeviceMirror; } + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + inline XnBool GetDeviceMirror() const { return (XnBool)m_DeviceMirror.GetValue(); } + + inline XnDeviceModule* DeviceModule() { return m_pDevicePropertiesHolder->GetModule(); } + inline XnDeviceModuleHolder* DeviceModuleHolder() { return m_pDevicePropertiesHolder; } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + virtual XnStatus SetMirror(XnBool bMirror); + + //--------------------------------------------------------------------------- + // IXnDevice Methods + //--------------------------------------------------------------------------- + virtual XnStatus Init(const XnDeviceConfig* pDeviceConfig); + virtual XnStatus Destroy(); + virtual XnStatus GetSupportedStreams(const XnChar** aStreamNames, XnUInt32* pnStreamNamesCount); + virtual XnStatus CreateStream(const XnChar* StreamType, const XnChar* StreamName = NULL, const XnPropertySet* pInitialValues = NULL); + virtual XnStatus DestroyStream(const XnChar* StreamName); + virtual XnStatus OpenStream(const XnChar* StreamName); + virtual XnStatus CloseStream(const XnChar* StreamName); + virtual XnStatus GetStreamNames(const XnChar** pstrNames, XnUInt32* pnNamesCount); + virtual XnStatus DoesModuleExist(const XnChar* ModuleName, XnBool* pbDoesExist); + virtual XnStatus OpenAllStreams(); + virtual XnStatus CloseAllStreams(); + virtual XnStatus RegisterToNewStreamData(XnDeviceOnNewStreamDataEventHandler Handler, void* pCookie, XnCallbackHandle& hCallback); + virtual XnStatus UnregisterFromNewStreamData(XnCallbackHandle hCallback); + virtual XnStatus DoesPropertyExist(const XnChar* ModuleName, XnUInt32 propertyId, XnBool* pbDoesExist); + virtual XnStatus GetPropertyType(const XnChar* ModuleName, XnUInt32 propertyId, XnPropertyType* pnType); + virtual XnStatus SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnUInt64 nValue); + virtual XnStatus SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnDouble dValue); + virtual XnStatus SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, const XnChar* csValue); + virtual XnStatus SetProperty(const XnChar* ModuleName, XnUInt32 propertyId, const OniGeneralBuffer& Value); + virtual XnStatus GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnUInt64* pnValue); + virtual XnStatus GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnDouble* pdValue); + virtual XnStatus GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, XnChar* csValue); + virtual XnStatus GetProperty(const XnChar* ModuleName, XnUInt32 propertyId, const OniGeneralBuffer& pValue); + virtual XnStatus LoadConfigFromFile(const XnChar* csINIFilePath, const XnChar* csSectionName); + virtual XnStatus BatchConfig(const XnPropertySet* pChangeSet); + virtual XnStatus GetAllProperties(XnPropertySet* pSet, XnBool bNoStreams = FALSE, const XnChar* strModule = NULL); + virtual XnStatus RegisterToPropertyChange(const XnChar* Module, XnUInt32 propertyId, XnDeviceOnPropertyChangedEventHandler Handler, void* pCookie, XnCallbackHandle& hCallback); + virtual XnStatus UnregisterFromPropertyChange(const XnChar* Module, XnUInt32 propertyId, XnCallbackHandle hCallback); + + typedef xnl::Event NewStreamDataEvent; + NewStreamDataEvent::EventInterface& OnNewStreamDataEvent() { return m_OnNewStreamDataEvent; } + + /** + * Finds a stream (a module which has the IS_STREAM property set to TRUE). + */ + XnStatus FindStream(const XnChar* StreamName, XnDeviceStream** ppStream); + +protected: + virtual XnStatus InitImpl(const XnDeviceConfig* pDeviceConfig); + virtual XnStatus CreateStreamImpl(const XnChar* strType, const XnChar* strName, const XnActualPropertiesHash* pInitialSet); + + virtual XnStatus CreateModule(const XnChar* strName, XnDeviceModuleHolder** ppModuleHolder); + virtual XnStatus CreateDeviceModule(XnDeviceModuleHolder** ppModuleHolder); + virtual void DestroyModule(XnDeviceModuleHolder* pModuleHolder); + + XnStatus CreateStreams(const XnPropertySet* pSet); + + /** + * Adds a module to the device modules. + */ + XnStatus AddModule(XnDeviceModuleHolder* pModuleHolder); + + /** + * Removes a module from the device modules. + */ + XnStatus RemoveModule(const XnChar* ModuleName); + + /** + * Finds a module. + */ + XnStatus FindModule(const XnChar* ModuleName, XnDeviceModule** ppModule); + + /** + * Finds a module. + */ + XnStatus FindModule(const XnChar* ModuleName, XnDeviceModuleHolder** ppModuleHolder); + + /** + * Checks if a module is a stream. + */ + static XnBool IsStream(XnDeviceModule* pModule); + + /** + * Finds a stream holder (a module which has the IS_STREAM property set to TRUE). + */ + XnStatus FindStream(const XnChar* StreamName, XnDeviceModuleHolder** ppStreamHolder); + + /** + * Adds a stream to the list of supported streams. + */ + XnStatus AddSupportedStream(const XnChar* StreamType); + + /** + * Creates a stream. + * + * @param StreamType [in] Type of the stream to create. + * @param StreamName [in] The name of the new stream. + */ + virtual XnStatus CreateStreamModule(const XnChar* StreamType, const XnChar* StreamName, XnDeviceModuleHolder** ppStreamHolder) = 0; + + virtual void DestroyStreamModule(XnDeviceModuleHolder* pStreamHolder) = 0; + + /** + * Gets the required output size of a stream. + */ + XnStatus GetStreamRequiredDataSize(const XnChar* StreamName, XnUInt32* pnRequiredSize); + + /** + * Gets the list of modules the device supports. + * + * @param aModules [out] an array of modules. + * @param pnModules [out] The number of modules. + */ + XnStatus GetModulesList(XnDeviceModuleHolder** apModules, XnUInt32* pnCount); + XnStatus GetModulesList(XnDeviceModuleHolderList& list); + + XnStatus GetStreamsList(XnDeviceModuleHolderList& list); + + /** + * Raises the NewStreamData event. + * + * @param StreamName [in] The name of the stream with new data. + */ + XnStatus RaiseNewStreamDataEvent(const XnChar* StreamName, OniFrame* pFrame); + + /** Gets called when a stream has new data. */ + virtual void OnNewStreamData(XnDeviceStream* pStream, OniFrame* pFrame); + + XnStatus ValidateOnlyModule(const XnPropertySet* pSet, const XnChar* StreamName); + +private: + void FreeModuleRegisteredProperties(const XnChar* strModule); + + static XnStatus XN_CALLBACK_TYPE PropertyValueChangedCallback(const XnProperty* pSender, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetMirrorCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + static void NewStreamDataCallback(XnDeviceStream* pSender, OniFrame* pFrame, void* pCookie); + + XnDeviceModuleHolder* m_pDevicePropertiesHolder; + XnActualIntProperty m_DeviceMirror; + + static XnStatus XN_CALLBACK_TYPE StreamNewDataCallback(XnDeviceStream* pStream, void* pCookie); + + typedef XnStringsHashT ModuleHoldersHash; + ModuleHoldersHash m_Modules; + + XnStringsSet m_SupportedStreams; + + struct XnPropertyCallback + { + XnPropertyCallback(const XnChar* strModule, XnUInt32 propertyId, XnDeviceOnPropertyChangedEventHandler pHandler, void* pCookie); + + XnChar strModule[XN_DEVICE_MAX_STRING_LENGTH]; + XnUInt32 propertyId; + void* pCookie; + XnDeviceOnPropertyChangedEventHandler pHandler; + XnCallbackHandle hCallback; + }; + typedef xnl::List PropertiesCallbacks; + PropertiesCallbacks m_PropertyCallbacks; + + NewStreamDataEvent m_OnNewStreamDataEvent; + + XnDumpFile* m_StreamsDataDump; + + XN_CRITICAL_SECTION_HANDLE m_hLock; +}; + +#endif //__XN_DEVICE_BASE_H__ diff --git a/Source/Drivers/PS1080/DDK/XnDeviceModule.cpp b/Source/Drivers/PS1080/DDK/XnDeviceModule.cpp new file mode 100644 index 0000000..463225a --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceModule.cpp @@ -0,0 +1,737 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceModule.h" +#include +#include +#include +#include "XnActualIntProperty.h" +#include "XnActualRealProperty.h" +#include "XnActualStringProperty.h" +#include "XnActualGeneralProperty.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnDeviceModule::XnDeviceModule(const XnChar* strName) : + m_Lock(XN_MODULE_PROPERTY_LOCK, "Lock", FALSE, strName), + m_hLockCS(NULL) +{ + strncpy(m_strName, strName, XN_DEVICE_MAX_STRING_LENGTH); + m_Lock.UpdateSetCallback(SetLockStateCallback, this); +} + +XnDeviceModule::~XnDeviceModule() +{ + Free(); +} + +XnStatus XnDeviceModule::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = AddProperty(&m_Lock); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = xnOSCreateCriticalSection(&m_hLockCS); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::Free() +{ + xnOSCloseCriticalSection(&m_hLockCS); + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::AddProperty(XnProperty* pProperty) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // make sure another property with this name doesn't exist + XnPropertiesHash::Iterator it = m_Properties.End(); + if (XN_STATUS_NO_MATCH != m_Properties.Find(pProperty->GetId(), it)) + return XN_STATUS_DEVICE_PROPERTY_ALREADY_EXISTS; + + nRetVal = m_Properties.Set(pProperty->GetId(), pProperty); + XN_IS_STATUS_OK(nRetVal); + + pProperty->UpdateName(GetName(), pProperty->GetName()); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::AddProperties(XnProperty** apProperties, XnUInt32 nCount) +{ + XnStatus nRetVal = XN_STATUS_OK; + + for (XnUInt32 i = 0; i < nCount; ++i) + { + nRetVal = AddProperty(apProperties[i]); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::DoesPropertyExist(XnUInt32 propertyId, XnBool* pbDoesExist) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + *pbDoesExist = FALSE; + + XnPropertiesHash::ConstIterator it = m_Properties.End(); + nRetVal = m_Properties.Find(propertyId, it); + if (nRetVal != XN_STATUS_NO_MATCH && nRetVal != XN_STATUS_OK) + { + return (nRetVal); + } + + *pbDoesExist = (nRetVal == XN_STATUS_OK); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::GetPropertyType(XnUInt32 propertyId, XnPropertyType* pnType) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + *pnType = pProp->GetType(); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::GetPropertyImpl(XnUInt32 propertyId, XnPropertyType Type, XnProperty** ppProperty) const +{ + *ppProperty = NULL; + + XnProperty* pProperty; + if (XN_STATUS_NO_MATCH == m_Properties.Get(propertyId, pProperty)) + { + return XN_STATUS_DEVICE_PROPERTY_DONT_EXIST; + } + + if (pProperty->GetType() != Type) + { + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + *ppProperty = pProperty; + return XN_STATUS_OK; +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnProperty **ppProperty) const +{ + XnProperty* pProperty; + if (XN_STATUS_NO_MATCH == m_Properties.Get(propertyId, pProperty)) + { + return XN_STATUS_DEVICE_PROPERTY_DONT_EXIST; + } + + *ppProperty = pProperty; + + return XN_STATUS_OK; +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnUInt64* pnValue) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnIntProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->GetValue(pnValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnDouble* pdValue) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnRealProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->GetValue(pdValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnChar* csValue) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnStringProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->GetValue(csValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); + +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, const OniGeneralBuffer& gbValue) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnGeneralProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->GetValue(gbValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, void* data, int* pDataSize) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnPropertyType type; + nRetVal = GetPropertyType(propertyId, &type); + XN_IS_STATUS_OK(nRetVal); + + switch (type) + { + case XN_PROPERTY_TYPE_INTEGER: + { + XnUInt64 nValue; + nRetVal = GetProperty(propertyId, &nValue); + if (nRetVal != XN_STATUS_OK) + { + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + if (*pDataSize == sizeof(XnUInt64)) + { + *(XnUInt64*)data = nValue; + } + else if (*pDataSize == sizeof(XnUInt32)) + { + *(XnUInt32*)data = (XnUInt32)nValue; + } + else if (*pDataSize == sizeof(XnUInt16)) + { + *(XnUInt16*)data = (XnUInt16)nValue; + } + else if (*pDataSize == sizeof(XnUInt8)) + { + *(XnUInt8*)data = (XnUInt8)nValue; + } + else + { + XN_ASSERT(FALSE); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + break; + } + case XN_PROPERTY_TYPE_REAL: + { + XnDouble dValue; + nRetVal = GetProperty(propertyId, &dValue); + if (nRetVal != XN_STATUS_OK) + { + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + if (*pDataSize == sizeof(XnDouble)) + { + *(XnDouble*)data = dValue; + } + else if (*pDataSize == sizeof(XnFloat)) + { + *(XnFloat*)data = (XnFloat)dValue; + } + else + { + XN_ASSERT(FALSE); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + break; + } + case XN_PROPERTY_TYPE_STRING: + { + XnChar strValue[XN_DEVICE_MAX_STRING_LENGTH]; + nRetVal = GetProperty(propertyId, strValue); + if (nRetVal != XN_STATUS_OK) + { + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + nRetVal = xnOSStrCopy((XnChar*)data, strValue, *pDataSize); + if (nRetVal != XN_STATUS_OK) + { + // wrong size? + XN_ASSERT(FALSE); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + break; + } + case XN_PROPERTY_TYPE_GENERAL: + { + OniGeneralBuffer buffer; + buffer.data = data; + buffer.dataSize = *pDataSize; + nRetVal = GetProperty(propertyId, buffer); + XN_IS_STATUS_OK(nRetVal); + break; + } + default: + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + return XN_STATUS_OK; +} + +XnStatus XnDeviceModule::SetProperty(XnUInt32 propertyId, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnIntProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->SetValue(nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::SetProperty(XnUInt32 propertyId, XnDouble dValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnRealProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->SetValue(dValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::SetProperty(XnUInt32 propertyId, const XnChar* strValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnStringProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->SetValue(strValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::SetProperty(XnUInt32 propertyId, const OniGeneralBuffer& gbValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnGeneralProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->SetValue(gbValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::SetProperty(XnUInt32 propertyId, const void* data, int dataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnPropertyType type; + nRetVal = GetPropertyType(propertyId, &type); + XN_IS_STATUS_OK(nRetVal); + + switch (type) + { + case XN_PROPERTY_TYPE_INTEGER: + { + XnUInt64 nValue; + + if (dataSize == sizeof(XnUInt64)) + { + nValue = *(XnUInt64*)data; + } + else if (dataSize == sizeof(XnUInt32)) + { + nValue = *(XnUInt32*)data; + } + else if (dataSize == sizeof(XnUInt16)) + { + nValue = *(XnUInt16*)data; + } + else if (dataSize == sizeof(XnUInt8)) + { + nValue = *(XnUInt8*)data; + } + else + { + XN_ASSERT(FALSE); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + nRetVal = SetProperty(propertyId, nValue); + XN_IS_STATUS_OK(nRetVal); + + break; + } + case XN_PROPERTY_TYPE_REAL: + { + XnDouble dValue; + + if (dataSize == sizeof(XnDouble)) + { + dValue = *(XnDouble*)data; + } + else if (dataSize == sizeof(XnFloat)) + { + dValue = *(XnFloat*)data; + } + else + { + XN_ASSERT(FALSE); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + nRetVal = SetProperty(propertyId, dValue); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_STRING: + { + nRetVal = SetProperty(propertyId, (const XnChar*)data); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_GENERAL: + { + OniGeneralBuffer buffer; + buffer.data = (void*)data; + buffer.dataSize = dataSize; + nRetVal = SetProperty(propertyId, buffer); + XN_IS_STATUS_OK(nRetVal); + break; + } + default: + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + return XN_STATUS_OK; +} + +XnStatus XnDeviceModule::UnsafeUpdateProperty(XnUInt32 propertyId, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnIntProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->UnsafeUpdateValue(nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::UnsafeUpdateProperty(XnUInt32 propertyId, XnDouble dValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnRealProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->UnsafeUpdateValue(dValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::UnsafeUpdateProperty(XnUInt32 propertyId, const XnChar* strValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnStringProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->UnsafeUpdateValue(strValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::UnsafeUpdateProperty(XnUInt32 propertyId, const OniGeneralBuffer& gbValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnGeneralProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->UnsafeUpdateValue(gbValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::RegisterForOnPropertyValueChanged(XnUInt32 propertyId, XnProperty::OnValueChangedHandler pFunc, void* pCookie, XnCallbackHandle& hCallback) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->OnChangeEvent().Register(pFunc, pCookie, hCallback); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::UnregisterFromOnPropertyValueChanged(XnUInt32 propertyId, XnCallbackHandle hCallback) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnProperty* pProp; + nRetVal = GetProperty(propertyId, &pProp); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->OnChangeEvent().Unregister(hCallback); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnIntProperty **ppIntProperty) const +{ + return GetPropertyImpl(propertyId, XN_PROPERTY_TYPE_INTEGER, (XnProperty**)ppIntProperty); +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnRealProperty **ppRealProperty) const +{ + return GetPropertyImpl(propertyId, XN_PROPERTY_TYPE_REAL, (XnProperty**)ppRealProperty); +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnStringProperty **ppStringProperty) const +{ + return GetPropertyImpl(propertyId, XN_PROPERTY_TYPE_STRING, (XnProperty**)ppStringProperty); +} + +XnStatus XnDeviceModule::GetProperty(XnUInt32 propertyId, XnGeneralProperty **ppPtrProperty) const +{ + return GetPropertyImpl(propertyId, XN_PROPERTY_TYPE_GENERAL, (XnProperty**)ppPtrProperty); +} + +XnStatus XnDeviceModule::GetAllProperties(XnPropertySet* pSet) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + // add the module + nRetVal = XnPropertySetAddModule(pSet, GetName()); + XN_IS_STATUS_OK(nRetVal); + + // now add all properties + for (XnPropertiesHash::ConstIterator it = m_Properties.Begin(); it != m_Properties.End(); ++it) + { + XnProperty* pProperty = it->Value(); + + if (pProperty->IsActual()) + { + nRetVal = pProperty->AddToPropertySet(pSet); + XN_IS_STATUS_OK(nRetVal); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::LoadConfigFromFile(const XnChar* csINIFilePath, const XnChar* strSectionName /* = NULL */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (strSectionName == NULL) + { + strSectionName = GetName(); + } + + xnLogVerbose(XN_MASK_DDK, "Configuring module '%s' from section '%s' in file '%s'...", GetName(), strSectionName, csINIFilePath); + + for (XnPropertiesHash::Iterator it = m_Properties.Begin(); it != m_Properties.End(); ++it) + { + XnProperty* pProp = it->Value(); + + // only read writable properties + if (!pProp->IsReadOnly()) + { + nRetVal = pProp->ReadValueFromFile(csINIFilePath, strSectionName); + XN_IS_STATUS_OK(nRetVal); + } + } + + xnLogInfo(XN_MASK_DDK, "Module '%s' configuration was loaded from file.", GetName()); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::BatchConfig(const XnActualPropertiesHash& props) +{ + XnStatus nRetVal = XN_STATUS_OK; + + for (XnActualPropertiesHash::ConstIterator it = props.Begin(); it != props.End(); ++it) + { + XnProperty* pRequestProp = it->Value(); + switch (pRequestProp->GetType()) + { + case XN_PROPERTY_TYPE_INTEGER: + { + XnActualIntProperty* pProp = (XnActualIntProperty*)pRequestProp; + nRetVal = SetProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_REAL: + { + XnActualRealProperty* pProp = (XnActualRealProperty*)pRequestProp; + nRetVal = SetProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_STRING: + { + XnActualStringProperty* pProp = (XnActualStringProperty*)pRequestProp; + nRetVal = SetProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_GENERAL: + { + XnActualGeneralProperty* pProp = (XnActualGeneralProperty*)pRequestProp; + nRetVal = SetProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Unknown property type: %d\n", pRequestProp->GetType()); + } // type switch + } // props loop + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::UnsafeBatchConfig(const XnActualPropertiesHash& props) +{ + XnStatus nRetVal = XN_STATUS_OK; + + for (XnActualPropertiesHash::ConstIterator it = props.Begin(); it != props.End(); ++it) + { + XnProperty* pRequestProp = it->Value(); + switch (pRequestProp->GetType()) + { + case XN_PROPERTY_TYPE_INTEGER: + { + XnActualIntProperty* pProp = (XnActualIntProperty*)pRequestProp; + nRetVal = UnsafeUpdateProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_REAL: + { + XnActualRealProperty* pProp = (XnActualRealProperty*)pRequestProp; + nRetVal = UnsafeUpdateProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_STRING: + { + XnActualStringProperty* pProp = (XnActualStringProperty*)pRequestProp; + nRetVal = UnsafeUpdateProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_GENERAL: + { + XnActualGeneralProperty* pProp = (XnActualGeneralProperty*)pRequestProp; + nRetVal = UnsafeUpdateProperty(pProp->GetId(), pProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Unknown property type: %d\n", pRequestProp->GetType()); + } // type switch + } // props loop + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModule::SetLockState(XnBool bLocked) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (bLocked && m_Lock.GetValue() == TRUE) + { + return XN_STATUS_NODE_IS_LOCKED; + } + + xnOSEnterCriticalSection(&m_hLockCS); + + // check again + if (bLocked && m_Lock.GetValue() == TRUE) + { + xnOSLeaveCriticalSection(&m_hLockCS); + return XN_STATUS_NODE_IS_LOCKED; + } + + nRetVal = m_Lock.UnsafeUpdateValue(bLocked); + xnOSLeaveCriticalSection(&m_hLockCS); + + return (nRetVal); +} + +XnStatus XnDeviceModule::SetLockStateCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnDeviceModule* pThis = (XnDeviceModule*)pCookie; + return pThis->SetLockState(nValue != FALSE); +} diff --git a/Source/Drivers/PS1080/DDK/XnDeviceModule.h b/Source/Drivers/PS1080/DDK/XnDeviceModule.h new file mode 100644 index 0000000..4ae67bf --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceModule.h @@ -0,0 +1,117 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEVICE_MODULE_H__ +#define __XN_DEVICE_MODULE_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** +* Holds a set of properties related to a specific module. +*/ +class XnDeviceModule +{ +public: + XnDeviceModule(const XnChar* strName); + virtual ~XnDeviceModule(); + + virtual XnStatus Init(); + virtual XnStatus Free(); + + inline const XnChar* GetName() const { return m_strName; } + + XnStatus AddProperty(XnProperty* pProperty); + XnStatus AddProperties(XnProperty** apProperties, XnUInt32 nCount); + + XnStatus DoesPropertyExist(XnUInt32 propertyId, XnBool* pbDoesExist) const; + XnStatus GetPropertyType(XnUInt32 propertyId, XnPropertyType* pnType) const; + + XnStatus GetProperty(XnUInt32 propertyId, XnProperty** ppProperty) const; + + virtual XnStatus GetProperty(XnUInt32 propertyId, XnUInt64* pnValue) const; + virtual XnStatus GetProperty(XnUInt32 propertyId, XnDouble* pdValue) const; + virtual XnStatus GetProperty(XnUInt32 propertyId, XnChar* csValue) const; + virtual XnStatus GetProperty(XnUInt32 propertyId, const OniGeneralBuffer& gbValue) const; + virtual XnStatus GetProperty(XnUInt32 propertyId, void* data, int* pDataSize) const; + + virtual XnStatus SetProperty(XnUInt32 propertyId, XnUInt64 nValue); + virtual XnStatus SetProperty(XnUInt32 propertyId, XnDouble dValue); + virtual XnStatus SetProperty(XnUInt32 propertyId, const XnChar* strValue); + virtual XnStatus SetProperty(XnUInt32 propertyId, const OniGeneralBuffer& gbValue); + virtual XnStatus SetProperty(XnUInt32 propertyId, const void* data, int dataSize); + + virtual XnStatus UnsafeUpdateProperty(XnUInt32 propertyId, XnUInt64 nValue); + virtual XnStatus UnsafeUpdateProperty(XnUInt32 propertyId, XnDouble dValue); + virtual XnStatus UnsafeUpdateProperty(XnUInt32 propertyId, const XnChar* strValue); + virtual XnStatus UnsafeUpdateProperty(XnUInt32 propertyId, const OniGeneralBuffer& gbValue); + + XnStatus GetAllProperties(XnPropertySet* pSet) const; + + XnStatus RegisterForOnPropertyValueChanged(XnUInt32 propertyId, XnProperty::OnValueChangedHandler pFunc, void* pCookie, XnCallbackHandle& hCallback); + XnStatus UnregisterFromOnPropertyValueChanged(XnUInt32 propertyId, XnCallbackHandle hCallback); + + /** + * Reads values for all properties in module from an INI file. + */ + XnStatus LoadConfigFromFile(const XnChar* csINIFilePath, const XnChar* strSectionName = NULL); + + virtual XnStatus BatchConfig(const XnActualPropertiesHash& props); + virtual XnStatus UnsafeBatchConfig(const XnActualPropertiesHash& props); + + XnStatus GetProperty(XnUInt32 propertyId, XnIntProperty** ppIntProperty) const; + XnStatus GetProperty(XnUInt32 propertyId, XnRealProperty** ppRealProperty) const; + XnStatus GetProperty(XnUInt32 propertyId, XnStringProperty** ppStringProperty) const; + XnStatus GetProperty(XnUInt32 propertyId, XnGeneralProperty** ppGeneralProperty) const; + +private: + XnStatus GetPropertyImpl(XnUInt32 propertyId, XnPropertyType Type, XnProperty** ppProperty) const; + + XnStatus SetLockState(XnBool bLocked); + + static XnStatus XN_CALLBACK_TYPE SetLockStateCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + XnChar m_strName[XN_DEVICE_MAX_STRING_LENGTH]; + + XnPropertiesHash m_Properties; + XnActualIntProperty m_Lock; + XN_CRITICAL_SECTION_HANDLE m_hLockCS; +}; + +#define XN_VALIDATE_ADD_PROPERTIES(pModule, ...) \ + { \ + XnProperty* _aProps[] = { __VA_ARGS__ }; \ + XnStatus _nRetVal = (pModule)->AddProperties(_aProps, sizeof(_aProps)/sizeof(XnProperty*)); \ + XN_IS_STATUS_OK(_nRetVal); \ + } + + +#endif //__XN_DEVICE_MODULE_H__ diff --git a/Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.cpp b/Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.cpp new file mode 100644 index 0000000..55b201e --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.cpp @@ -0,0 +1,56 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "XnDeviceModuleHolder.h" +#include "XnActualIntProperty.h" +#include "XnActualRealProperty.h" +#include "XnActualStringProperty.h" +#include "XnActualGeneralProperty.h" +#include + +XnDeviceModuleHolder::XnDeviceModuleHolder(XnDeviceModule* pModule) : + m_pModule(pModule) +{} + +XnDeviceModuleHolder::~XnDeviceModuleHolder() +{ + XnDeviceModuleHolder::Free(); +} + +XnStatus XnDeviceModuleHolder::Init(const XnActualPropertiesHash* pInitialValues) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_pModule->Init(); + XN_IS_STATUS_OK(nRetVal); + + if (pInitialValues != NULL) + { + nRetVal = m_pModule->BatchConfig(*pInitialValues); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceModuleHolder::Free() +{ + return XN_STATUS_OK; +} diff --git a/Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.h b/Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.h new file mode 100644 index 0000000..2ba877a --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceModuleHolder.h @@ -0,0 +1,54 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEVICE_MODULE_HOLDER_H__ +#define __XN_DEVICE_MODULE_HOLDER_H__ + +#include "XnActualPropertiesHash.h" +#include "XnDeviceModule.h" +#include + +class XnDeviceModuleHolder +{ +public: + /** + * Creates a new module holder. + * + * @param pModule [in] The actual module. + * @param bAllowNewProps [in] When TRUE, Init() method will create non-existing properties. + */ + XnDeviceModuleHolder(XnDeviceModule* pModule); + virtual ~XnDeviceModuleHolder(); + + virtual XnStatus Init(const XnActualPropertiesHash* pInitialValues); + + inline XnDeviceModule* GetModule() const { return m_pModule; } + +protected: + virtual XnStatus Free(); + +private: + XnDeviceModule* m_pModule; +}; + +typedef xnl::List XnDeviceModuleHolderList; + + +#endif //__XN_DEVICE_MODULE_HOLDER_H__ diff --git a/Source/Drivers/PS1080/DDK/XnDeviceStream.cpp b/Source/Drivers/PS1080/DDK/XnDeviceStream.cpp new file mode 100644 index 0000000..2f248d4 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceStream.cpp @@ -0,0 +1,230 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceStream.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnDeviceStream::XnDeviceStream(const XnChar* csType, const XnChar* csName) : + XnDeviceModule(csName), + m_pServices(NULL), + m_IsStream(XN_STREAM_PROPERTY_IS_STREAM, "IsStream", TRUE), + m_Type(XN_STREAM_PROPERTY_TYPE, "Type", csType), + m_IsOpen(XN_STREAM_PROPERTY_STATE, "State", FALSE), + m_RequiredSize(XN_STREAM_PROPERTY_REQUIRED_DATA_SIZE, "RequiredDataSize"), + m_OutputFormat(XN_STREAM_PROPERTY_OUTPUT_FORMAT, "OutputFormat"), + m_IsMirrored(XN_MODULE_PROPERTY_MIRROR, "Mirror"), + m_pNewDataCallback(NULL), + m_nRefCount(1), + m_nOpenRefCount(0), + m_hCriticalSection(NULL), + m_hOpenLock(NULL) +{ +} + +XnStatus XnDeviceStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init module + nRetVal = XnDeviceModule::Init(); + XN_IS_STATUS_OK(nRetVal); + + // cs + nRetVal = xnOSCreateCriticalSection(&m_hCriticalSection); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = xnOSCreateCriticalSection(&m_hOpenLock); + XN_IS_STATUS_OK(nRetVal); + + m_IsOpen.UpdateSetCallback(SetIsOpenCallback, this); + m_OutputFormat.UpdateSetCallback(SetOutputFormatCallback, this); + m_IsMirrored.UpdateSetCallback(SetIsMirrorCallback, this); + + XN_VALIDATE_ADD_PROPERTIES(this, &m_IsStream, &m_Type, &m_IsOpen, &m_OutputFormat, &m_RequiredSize, &m_IsMirrored); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceStream::Free() +{ + xnOSCloseCriticalSection(&m_hCriticalSection); + xnOSCloseCriticalSection(&m_hOpenLock); + XnDeviceModule::Free(); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceStream::Open() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_IsOpen.UnsafeUpdateValue(TRUE); + XN_IS_STATUS_OK(nRetVal); + + m_nOpenRefCount = 1; + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceStream::Close() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_IsOpen.UnsafeUpdateValue(FALSE); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceStream::SetOutputFormat(OniPixelFormat nOutputFormat) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_OutputFormat.UnsafeUpdateValue(nOutputFormat); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceStream::SetMirror(XnBool bIsMirrored) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_IsMirrored.UnsafeUpdateValue(bIsMirrored); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnDeviceStream::NewDataAvailable(OniFrame* pFrame) +{ + xnOSEnterCriticalSection(&m_hCriticalSection); + XnBool bMirror = IsMirrored(); + xnOSLeaveCriticalSection(&m_hCriticalSection); + + // mirror it if needed + if (bMirror) + { + Mirror(pFrame); + } + + // make sure someone is listening (otherwise, no one will ever free this frame!) + XN_ASSERT(m_pNewDataCallback != NULL); + m_pNewDataCallback(this, pFrame, m_pNewDataCallbackCookie); +} + +void XnDeviceStream::SetNewDataCallback(NewDataCallbackPtr pFunc, void* pCookie) +{ + m_pNewDataCallback = pFunc; + m_pNewDataCallbackCookie = pCookie; +} + +XnStatus XnDeviceStream::RegisterRequiredSizeProperty(XnProperty* pProperty) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // make sure the property belongs to this module (because we never unregister from it) + XN_ASSERT(strcmp(pProperty->GetModule(), GetName()) == 0); + + XnCallbackHandle hCallbackDummy; + nRetVal = pProperty->OnChangeEvent().Register(UpdateRequiredSizeCallback, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + // recalculate it + nRetVal = UpdateRequiredSize(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceStream::UpdateRequiredSize() +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt32 nRequiredSize; + nRetVal = CalcRequiredSize(&nRequiredSize); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_RequiredSize.UnsafeUpdateValue(nRequiredSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XN_CALLBACK_TYPE XnDeviceStream::UpdateRequiredSizeCallback(const XnProperty* /*pSenser*/, void* pCookie) +{ + XnDeviceStream* pStream = (XnDeviceStream*)pCookie; + return pStream->UpdateRequiredSize(); +} + +XnStatus XN_CALLBACK_TYPE XnDeviceStream::SetIsOpenCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnDeviceStream* pStream = (XnDeviceStream*)pCookie; + if (nValue == TRUE) + { + return pStream->Open(); + } + else + { + return pStream->Close(); + } +} + +XnStatus XN_CALLBACK_TYPE XnDeviceStream::SetOutputFormatCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnDeviceStream* pStream = (XnDeviceStream*)pCookie; + return pStream->SetOutputFormat((OniPixelFormat)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnDeviceStream::SetIsMirrorCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnDeviceStream* pStream = (XnDeviceStream*)pCookie; + return pStream->SetMirror((XnBool)nValue); +} + +void XnDeviceStream::AddRef() +{ + xnl::AutoCSLocker lock(m_hCriticalSection); + ++m_nRefCount; +} + +XnUInt32 XnDeviceStream::DecRef() +{ + xnl::AutoCSLocker lock(m_hCriticalSection); + return --m_nRefCount; +} + +void XnDeviceStream::OpenAddRef() +{ + xnl::AutoCSLocker lock(m_hOpenLock); + ++m_nOpenRefCount; +} + +XnUInt32 XnDeviceStream::OpenDecRef() +{ + xnl::AutoCSLocker lock(m_hOpenLock); + return --m_nOpenRefCount; +} diff --git a/Source/Drivers/PS1080/DDK/XnDeviceStream.h b/Source/Drivers/PS1080/DDK/XnDeviceStream.h new file mode 100644 index 0000000..1ce3a4c --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnDeviceStream.h @@ -0,0 +1,142 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEVICE_STREAM_H__ +#define __XN_DEVICE_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** Represents a stream in a device. */ +class XnDeviceStream : public XnDeviceModule +{ +public: + XnDeviceStream(const XnChar* csType, const XnChar* csName); + ~XnDeviceStream() { Free(); } + + XnStatus Init(); + XnStatus Free(); + + void SetServices(oni::driver::StreamServices& services) { m_pServices = &services; } + void AddRefToFrame(OniFrame* pFrame) { m_pServices->addFrameRef(pFrame); } + void ReleaseFrame(OniFrame* pFrame) { m_pServices->releaseFrame(pFrame); } + + void AddRef(); + XnUInt32 DecRef(); + + void OpenAddRef(); + XnUInt32 OpenDecRef(); + XnUInt32 GetOpenRefCount() const { return m_nOpenRefCount; } + + typedef void (*NewDataCallbackPtr)(XnDeviceStream* pSender, OniFrame* pFrame, void* pCookie); + + /** Sets a function callback to be called when new data is available. */ + void SetNewDataCallback(NewDataCallbackPtr pFunc, void* pCookie); + + /** Notifies new data is available in this stream. */ + virtual void NewDataAvailable(OniFrame* pFrame); + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + inline const XnChar* GetType() const { return m_Type.GetValue(); } + inline XnBool IsOpen() const { return (XnBool)m_IsOpen.GetValue(); } + inline OniPixelFormat GetOutputFormat() const { return (OniPixelFormat)m_OutputFormat.GetValue(); } + inline XnBool IsMirrored() const { return (XnBool)m_IsMirrored.GetValue(); } + inline XnUInt32 GetRequiredDataSize() const { return (XnUInt32)m_RequiredSize.GetValue(); } + inline XnUInt32 GetLastFrameID() const { return m_nFrameID; } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + virtual XnStatus Open(); + virtual XnStatus Close(); + virtual XnStatus SetOutputFormat(OniPixelFormat nOutputFormat); + virtual XnStatus SetMirror(XnBool bIsMirrored); + + inline XN_CRITICAL_SECTION_HANDLE* GetOpenLock() { return &m_hOpenLock; } + +protected: + //--------------------------------------------------------------------------- + // Properties Getters + //--------------------------------------------------------------------------- + inline XnActualIntProperty& IsOpenProperty() { return m_IsOpen; } + inline XnActualIntProperty& OutputFormatProperty() { return m_OutputFormat; } + inline XnActualIntProperty& RequiredSizeProperty() { return m_RequiredSize; } + inline XnActualIntProperty& IsMirroredProperty() { return m_IsMirrored; } + +protected: + oni::driver::StreamServices& GetServices() { return *m_pServices; } + + //--------------------------------------------------------------------------- + // Virtual Functions + //--------------------------------------------------------------------------- + + /** Performs mirror on the given stream output. */ + virtual XnStatus Mirror(OniFrame* pFrame) const = 0; + /** Calculates the required size. */ + virtual XnStatus CalcRequiredSize(XnUInt32* pnRequiredSize) const = 0; + + //--------------------------------------------------------------------------- + // Utility Functions + //--------------------------------------------------------------------------- + XnStatus RegisterRequiredSizeProperty(XnProperty* pProperty); + inline XN_CRITICAL_SECTION_HANDLE* GetLock() { return &m_hCriticalSection; } + +private: + XnStatus UpdateRequiredSize(); + + static XnStatus XN_CALLBACK_TYPE UpdateRequiredSizeCallback(const XnProperty* pSenser, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetIsOpenCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetOutputFormatCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetIsMirrorCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + oni::driver::StreamServices* m_pServices; + XnActualIntProperty m_IsStream; + XnActualStringProperty m_Type; + XnActualIntProperty m_IsOpen; + XnActualIntProperty m_RequiredSize; + XnActualIntProperty m_OutputFormat; + XnActualIntProperty m_IsMirrored; + + XnUInt32 m_nFrameID; + NewDataCallbackPtr m_pNewDataCallback; + void* m_pNewDataCallbackCookie; + + XnUInt32 m_nRefCount; + XnUInt32 m_nOpenRefCount; + XN_CRITICAL_SECTION_HANDLE m_hCriticalSection; + XN_CRITICAL_SECTION_HANDLE m_hOpenLock; +}; + +#endif //__XN_DEVICE_STREAM_H__ diff --git a/Source/Drivers/PS1080/DDK/XnFrameBufferManager.cpp b/Source/Drivers/PS1080/DDK/XnFrameBufferManager.cpp new file mode 100644 index 0000000..041d48a --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnFrameBufferManager.cpp @@ -0,0 +1,143 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFrameBufferManager.h" +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnFrameBufferManager::XnFrameBufferManager() : + m_pServices(NULL), + m_pWorkingBuffer(NULL), + m_nStableFrameID(0), + m_newFrameCallback(NULL), + m_newFrameCallbackCookie(NULL), + m_hLock(NULL) +{ +} + +XnFrameBufferManager::~XnFrameBufferManager() +{ + Free(); +} + +void XnFrameBufferManager::SetNewFrameCallback(NewFrameCallback func, void* pCookie) +{ + m_newFrameCallback = func; + m_newFrameCallbackCookie = pCookie; +} + +XnStatus XnFrameBufferManager::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSCreateCriticalSection(&m_hLock); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnFrameBufferManager::Free() +{ + Stop(); + + if (m_hLock != NULL) + { + xnOSCloseCriticalSection(&m_hLock); + m_hLock = NULL; + } +} + +XnStatus XnFrameBufferManager::Start(oni::driver::StreamServices& services) +{ + m_pServices = &services; + + // take working buffer + m_pWorkingBuffer = m_pServices->acquireFrame(); + if (m_pWorkingBuffer == NULL) + { + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + m_writeBuffer.SetExternalBuffer((XnUChar*)m_pWorkingBuffer->data, m_pWorkingBuffer->dataSize); + + return (XN_STATUS_OK); +} + +void XnFrameBufferManager::Stop() +{ + if (m_pWorkingBuffer != NULL) + { + m_pServices->releaseFrame(m_pWorkingBuffer); + m_pWorkingBuffer = NULL; + } + + m_pServices = NULL; +} + +void XnFrameBufferManager::MarkWriteBufferAsStable(XnUInt32* pnFrameID) +{ + xnOSEnterCriticalSection(&m_hLock); + + OniFrame* pStableBuffer = m_pWorkingBuffer; + pStableBuffer->dataSize = m_writeBuffer.GetSize(); + + // mark working as stable + m_nStableFrameID++; + *pnFrameID = m_nStableFrameID; + pStableBuffer->frameIndex = m_nStableFrameID; + + // take a new working buffer + m_pWorkingBuffer = m_pServices->acquireFrame(); + if (m_pWorkingBuffer == NULL) + { + xnLogError(XN_MASK_DDK, "Failed to get new working buffer!"); + + // we'll return back to our old working one + m_pWorkingBuffer = pStableBuffer; + m_pWorkingBuffer->dataSize = 0; + + XN_ASSERT(FALSE); + return; + } + + m_writeBuffer.SetExternalBuffer((XnUChar*)m_pWorkingBuffer->data, m_pWorkingBuffer->dataSize); + + xnOSLeaveCriticalSection(&m_hLock); + + // reset new working + m_pWorkingBuffer->dataSize = 0; + + // notify stream that new data is available + if (m_newFrameCallback != NULL) + { + m_newFrameCallback(pStableBuffer, m_newFrameCallbackCookie); + } + + // and release our reference + m_pServices->releaseFrame(pStableBuffer); +} + diff --git a/Source/Drivers/PS1080/DDK/XnFrameBufferManager.h b/Source/Drivers/PS1080/DDK/XnFrameBufferManager.h new file mode 100644 index 0000000..9e9d590 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnFrameBufferManager.h @@ -0,0 +1,80 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_MULTI_FRAME_BUFFER_H__ +#define __XN_MULTI_FRAME_BUFFER_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +class XnFrameBufferManager +{ +public: + typedef void (XN_CALLBACK_TYPE* NewFrameCallback)(OniFrame* pFrame, void* pCookie); + + XnFrameBufferManager(); + ~XnFrameBufferManager(); + + void SetNewFrameCallback(NewFrameCallback func, void* pCookie); + + XnStatus Init(); + void Free(); + + XnStatus Start(oni::driver::StreamServices& services); + void Stop(); + + inline XnBuffer* GetWriteBuffer() + { + // NOTE: no need to lock buffer, as we assume the same thread is the one that is responsible + // for marking working buffer as stable. + return &m_writeBuffer; + } + + inline OniFrame* GetWriteFrame() + { + return m_pWorkingBuffer; + } + + void MarkWriteBufferAsStable(XnUInt32* pnFrameID); + + inline XnUInt32 GetLastFrameID() const { return m_nStableFrameID; } + +private: + XN_DISABLE_COPY_AND_ASSIGN(XnFrameBufferManager); + + oni::driver::StreamServices* m_pServices; + OniFrame* m_pWorkingBuffer; + XnUInt32 m_nStableFrameID; + NewFrameCallback m_newFrameCallback; + void* m_newFrameCallbackCookie; + XN_CRITICAL_SECTION_HANDLE m_hLock; + XnBuffer m_writeBuffer; +}; + +#endif //__XN_MULTI_FRAME_BUFFER_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnFrameStream.cpp b/Source/Drivers/PS1080/DDK/XnFrameStream.cpp new file mode 100644 index 0000000..5988c09 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnFrameStream.cpp @@ -0,0 +1,104 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFrameStream.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnFrameStream::XnFrameStream(const XnChar* csType, const XnChar* csName) : + XnDeviceStream(csType, csName), + m_nLastReadFrame(0), + m_IsFrameStream(XN_STREAM_PROPERTY_IS_FRAME_BASED, "IsFrameBased", TRUE), + m_FPS(XN_STREAM_PROPERTY_FPS, "FPS", 0) +{ + m_FPS.UpdateSetCallback(SetFPSCallback, this); +} + +XnStatus XnFrameStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnDeviceStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_bufferManager.Init(); + XN_IS_STATUS_OK(nRetVal); + + // register for new data events + m_bufferManager.SetNewFrameCallback(OnTripleBufferNewData, this); + + XN_VALIDATE_ADD_PROPERTIES(this, &m_IsFrameStream, &m_FPS ); + + return (XN_STATUS_OK); +} + +XnStatus XnFrameStream::StartBufferManager(XnFrameBufferManager** pBufferManager) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_bufferManager.Start(GetServices()); + XN_IS_STATUS_OK(nRetVal); + + *pBufferManager = &m_bufferManager; + + return (XN_STATUS_OK); +} + +XnStatus XnFrameStream::Free() +{ + m_bufferManager.Free(); + XnDeviceStream::Free(); + return (XN_STATUS_OK); +} + +XnStatus XnFrameStream::SetFPS(XnUInt32 nFPS) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_FPS.UnsafeUpdateValue(nFPS); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XN_CALLBACK_TYPE XnFrameStream::SetFPSCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnFrameStream* pThis = (XnFrameStream*)pCookie; + return pThis->SetFPS((XnUInt32)nValue); +} + +void XN_CALLBACK_TYPE XnFrameStream::OnTripleBufferNewData(OniFrame* pFrame, void* pCookie) +{ + XnFrameStream* pThis = (XnFrameStream*)pCookie; + pThis->NewDataAvailable(pFrame); +} + +XnStatus XnFrameStream::Close() +{ + m_bufferManager.Stop(); + + return XnDeviceStream::Close(); +} + diff --git a/Source/Drivers/PS1080/DDK/XnFrameStream.h b/Source/Drivers/PS1080/DDK/XnFrameStream.h new file mode 100644 index 0000000..e20d4c4 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnFrameStream.h @@ -0,0 +1,93 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_FRAME_STREAM_H__ +#define __XN_FRAME_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceStream.h" +#include "XnFrameBufferManager.h" +#include "Driver/OniDriverTypes.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** Represents a stream that is frame-based. */ +class XnFrameStream : public XnDeviceStream +{ +public: + XnFrameStream(const XnChar* csType, const XnChar* csName); + ~XnFrameStream() { Free(); } + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + inline XnUInt32 GetFPS() const { return (XnUInt32)m_FPS.GetValue(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + XnStatus Free(); + + virtual XnStatus Close(); + +protected: + //--------------------------------------------------------------------------- + // Properties Getters + //--------------------------------------------------------------------------- + inline XnActualIntProperty& FPSProperty() { return m_FPS; } + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + XnStatus StartBufferManager(XnFrameBufferManager** pBufferManager); + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + virtual XnStatus SetFPS(XnUInt32 nFPS); + + //--------------------------------------------------------------------------- + // Virtual Methods + //--------------------------------------------------------------------------- + virtual XnStatus PostProcessFrame(OniFrame* /*pFrame*/) { return XN_STATUS_OK; } + +private: + XN_DISABLE_COPY_AND_ASSIGN(XnFrameStream); + + static XnStatus XN_CALLBACK_TYPE SetFPSCallback(XnActualIntProperty* pSenser, XnUInt64 nValue, void* pCookie); + static void XN_CALLBACK_TYPE OnTripleBufferNewData(OniFrame* pFrame, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnFrameBufferManager m_bufferManager; + + XnUInt32 m_nLastReadFrame; // the ID that was given + + XnActualIntProperty m_IsFrameStream; + XnActualIntProperty m_FPS; +}; + +#endif //__XN_FRAME_STREAM_H__ diff --git a/Source/Drivers/PS1080/DDK/XnGeneralProperty.cpp b/Source/Drivers/PS1080/DDK/XnGeneralProperty.cpp new file mode 100644 index 0000000..5912c14 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnGeneralProperty.cpp @@ -0,0 +1,82 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnGeneralProperty.h" +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnGeneralProperty::XnGeneralProperty(XnUInt32 propertyId, const XnChar* strName, OniGeneralBuffer* pValueHolder /* = NULL */, ReadValueFromFileFuncPtr pReadFromFileFunc /* = NULL */, const XnChar* strModule /* = "" */ ) : + XnProperty(XN_PROPERTY_TYPE_GENERAL, pValueHolder, propertyId, strName, strModule), + m_pReadFromFileFunc(pReadFromFileFunc) +{ +} + +XnStatus XnGeneralProperty::CopyValueImpl(void* pDest, const void* pSource) const +{ + return XnGeneralBufferCopy((OniGeneralBuffer*)pDest, (const OniGeneralBuffer*)pSource); +} + +XnBool XnGeneralProperty::IsEqual(const void* pValue1, const void* pValue2) const +{ + const OniGeneralBuffer* pgb1 = (const OniGeneralBuffer*)pValue1; + const OniGeneralBuffer* pgb2 = (const OniGeneralBuffer*)pValue2; + + if (pgb1->dataSize != pgb2->dataSize) + return FALSE; + + return (memcmp(pgb1->data, pgb2->data, pgb1->dataSize) == 0); +} + +XnStatus XnGeneralProperty::CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie) +{ + SetFuncPtr pCallback = (SetFuncPtr)pFunc; + return pCallback(this, *(const OniGeneralBuffer*)pValue, pCookie); +} + +XnStatus XnGeneralProperty::CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const +{ + GetFuncPtr pCallback = (GetFuncPtr)pFunc; + return pCallback(this, *(const OniGeneralBuffer*)pValue, pCookie); +} + +XnStatus XnGeneralProperty::ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_pReadFromFileFunc != NULL) + { + nRetVal = m_pReadFromFileFunc(this, csINIFile, csSection); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnGeneralProperty::AddToPropertySet(XnPropertySet* /*pSet*/) +{ + return (XN_STATUS_ERROR); +} diff --git a/Source/Drivers/PS1080/DDK/XnGeneralProperty.h b/Source/Drivers/PS1080/DDK/XnGeneralProperty.h new file mode 100644 index 0000000..8a79507 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnGeneralProperty.h @@ -0,0 +1,88 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_GENERAL_PROPERTY_H__ +#define __XN_GENERAL_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Class +//--------------------------------------------------------------------------- + +/** +* A property of type general. +*/ +class XnGeneralProperty : public XnProperty +{ +public: + typedef XnStatus (XN_CALLBACK_TYPE* ReadValueFromFileFuncPtr)(XnGeneralProperty* pSender, const XnChar* csINIFile, const XnChar* csSection); + + XnGeneralProperty(XnUInt32 propertyId, const XnChar* strName, OniGeneralBuffer* pValueHolder = NULL, ReadValueFromFileFuncPtr pReadFromFileFunc = NULL, const XnChar* strModule = ""); + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + + inline XnStatus SetValue(const OniGeneralBuffer& gbValue) + { + return XnProperty::SetValue(&gbValue); + } + + inline XnStatus GetValue(const OniGeneralBuffer& gbValue) const + { + return XnProperty::GetValue((void*)&gbValue); + } + + inline XnStatus UnsafeUpdateValue(const OniGeneralBuffer& gbValue) + { + return XnProperty::UnsafeUpdateValue(&gbValue); + } + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateSetCallback((XnProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateGetCallback((XnProperty::GetFuncPtr)pFunc, pCookie); + } + + XnStatus AddToPropertySet(XnPropertySet* pSet); + +protected: + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + virtual XnStatus CopyValueImpl(void* pDest, const void* pSource) const; + virtual XnBool IsEqual(const void* pValue1, const void* pValue2) const; + virtual XnStatus CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie); + virtual XnStatus CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const; + virtual XnStatus ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection); + +private: + ReadValueFromFileFuncPtr m_pReadFromFileFunc; +}; + +#endif //__XN_GENERAL_PROPERTY_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnIRStream.cpp b/Source/Drivers/PS1080/DDK/XnIRStream.cpp new file mode 100644 index 0000000..8858fe2 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnIRStream.cpp @@ -0,0 +1,32 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnIRStream.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnIRStream::XnIRStream(const XnChar* csName, XnBool bAllowCustomResolutions) : + XnPixelStream(XN_STREAM_TYPE_IR, csName, bAllowCustomResolutions) +{ +} diff --git a/Source/Drivers/PS1080/DDK/XnIRStream.h b/Source/Drivers/PS1080/DDK/XnIRStream.h new file mode 100644 index 0000000..fe132bb --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnIRStream.h @@ -0,0 +1,40 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_IR_STREAM_H__ +#define __XN_IR_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** Represents a base class for an image stream. */ +class XnIRStream : public XnPixelStream +{ +public: + XnIRStream(const XnChar* csName, XnBool bAllowCustomResolutions); +}; + +#endif //__XN_IR_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnImageStream.cpp b/Source/Drivers/PS1080/DDK/XnImageStream.cpp new file mode 100644 index 0000000..eabcad6 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnImageStream.cpp @@ -0,0 +1,32 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageStream.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnImageStream::XnImageStream(const XnChar* csName, XnBool bAllowCustomResolutions) : + XnPixelStream(XN_STREAM_TYPE_IMAGE, csName, bAllowCustomResolutions) +{ +} diff --git a/Source/Drivers/PS1080/DDK/XnImageStream.h b/Source/Drivers/PS1080/DDK/XnImageStream.h new file mode 100644 index 0000000..bcc524e --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnImageStream.h @@ -0,0 +1,40 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_IMAGE_STREAM_H__ +#define __XN_IMAGE_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** Represents a base class for an image stream. */ +class XnImageStream : public XnPixelStream +{ +public: + XnImageStream(const XnChar* csName, XnBool bAllowCustomResolutions); +}; + +#endif //__XN_IMAGE_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnIntProperty.cpp b/Source/Drivers/PS1080/DDK/XnIntProperty.cpp new file mode 100644 index 0000000..1786702 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnIntProperty.cpp @@ -0,0 +1,95 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnIntProperty.h" +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnIntProperty::XnIntProperty(XnUInt32 propertyId, const XnChar* strName, XnUInt64* pValueHolder /* = NULL */, const XnChar* strModule /* = "" */ ) : + XnProperty(XN_PROPERTY_TYPE_INTEGER, pValueHolder, propertyId, strName, strModule) +{ +} + +XnStatus XnIntProperty::ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnInt32 nValue; + + nRetVal = xnOSReadIntFromINI(csINIFile, csSection, GetName(), &nValue); + if (nRetVal == XN_STATUS_OK) + { + nRetVal = SetValue(nValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnIntProperty::CopyValueImpl(void* pDest, const void* pSource) const +{ + (*(XnUInt64*)pDest) = (*(const XnUInt64*)pSource); + return XN_STATUS_OK; +} + +XnBool XnIntProperty::IsEqual(const void* pValue1, const void* pValue2) const +{ + return (*(XnUInt64*)pValue1) == (*(XnUInt64*)pValue2); +} + +XnStatus XnIntProperty::CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie) +{ + SetFuncPtr pCallback = (SetFuncPtr)pFunc; + return pCallback(this, *(const XnUInt64*)pValue, pCookie); +} + +XnStatus XnIntProperty::CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const +{ + GetFuncPtr pCallback = (GetFuncPtr)pFunc; + return pCallback(this, (XnUInt64*)pValue, pCookie); +} + +XnBool XnIntProperty::ConvertValueToString(XnChar* csValue, const void* pValue) const +{ + sprintf(csValue, "%llu", *(XnUInt64*)pValue); + return TRUE; +} + +XnStatus XnIntProperty::AddToPropertySet(XnPropertySet* pSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt64 nValue; + nRetVal = GetValue(&nValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnPropertySetAddIntProperty(pSet, GetModule(), GetId(), nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + diff --git a/Source/Drivers/PS1080/DDK/XnIntProperty.h b/Source/Drivers/PS1080/DDK/XnIntProperty.h new file mode 100644 index 0000000..1986566 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnIntProperty.h @@ -0,0 +1,85 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_INT_PROPERTY_H__ +#define __XN_INT_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Class +//--------------------------------------------------------------------------- + +/** +* A property of type integer. +*/ +class XnIntProperty : public XnProperty +{ +public: + XnIntProperty(XnUInt32 propertyId, const XnChar* strName, XnUInt64* pValueHolder = NULL, const XnChar* strModule = ""); + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnIntProperty* pSender, XnUInt64 nValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnIntProperty* pSender, XnUInt64* pnValue, void* pCookie); + + inline XnStatus SetValue(XnUInt64 nValue) + { + return XnProperty::SetValue(&nValue); + } + + inline XnStatus GetValue(XnUInt64* pnValue) const + { + XN_VALIDATE_OUTPUT_PTR(pnValue); + return XnProperty::GetValue(pnValue); + } + + XnStatus UnsafeUpdateValue(XnUInt64 nValue) + { + return XnProperty::UnsafeUpdateValue(&nValue); + } + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateSetCallback((XnProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateGetCallback((XnProperty::GetFuncPtr)pFunc, pCookie); + } + + virtual XnStatus ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection); + + XnStatus AddToPropertySet(XnPropertySet* pSet); + +protected: + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + virtual XnStatus CopyValueImpl(void* pDest, const void* pSource) const; + virtual XnBool IsEqual(const void* pValue1, const void* pValue2) const; + virtual XnStatus CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie); + virtual XnStatus CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const; + virtual XnBool ConvertValueToString(XnChar* csValue, const void* pValue) const; +}; + +#endif //__XN_INT_PROPERTY_H__ diff --git a/Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.cpp b/Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.cpp new file mode 100644 index 0000000..3d6c6c9 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.cpp @@ -0,0 +1,116 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnIntPropertySynchronizer.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnIntSynchronizerCookie +{ +public: + XnIntSynchronizerCookie(XnIntProperty* pSource, XnIntProperty* pDestination, XnIntPropertyConvertCallback pConvertFunc) : + pSource(pSource), pDestination(pDestination), pConvertFunc(pConvertFunc) + {} + + XnIntProperty* pSource; + XnIntProperty* pDestination; + XnIntPropertyConvertCallback pConvertFunc; + XnCallbackHandle hCallback; +}; + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnIntPropertySynchronizer::XnIntPropertySynchronizer() +{ +} + +XnIntPropertySynchronizer::~XnIntPropertySynchronizer() +{ + for (CookiesList::Iterator it = m_Cookies.Begin(); it != m_Cookies.End(); ++it) + { + XnIntSynchronizerCookie* pSynchData = (XnIntSynchronizerCookie*)*it; + pSynchData->pSource->OnChangeEvent().Unregister(pSynchData->hCallback); + XN_DELETE(pSynchData); + } +} + +XnStatus XN_CALLBACK_TYPE IntPropertyValueChangedCallback(const XnProperty* pSender, void* pCookie) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get the property current value + XnIntProperty* pIntProp = (XnIntProperty*)pSender; + + XnUInt64 nNewValue; + nRetVal = pIntProp->GetValue(&nNewValue); + XN_IS_STATUS_OK(nRetVal); + + XnIntSynchronizerCookie* pSynchData = (XnIntSynchronizerCookie*)pCookie; + + XnUInt64 nDestValue; + + // convert the value if needed + if (pSynchData->pConvertFunc != NULL) + { + nRetVal = pSynchData->pConvertFunc(nNewValue, &nDestValue); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nDestValue = nNewValue; + } + + // now set the new value + nRetVal = pSynchData->pDestination->UnsafeUpdateValue(nDestValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnIntPropertySynchronizer::RegisterSynchronization(XnIntProperty* pSource, XnIntProperty* pDestination, XnIntPropertyConvertCallback pConvertFunc /* = NULL */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnIntSynchronizerCookie* pCookie; + XN_VALIDATE_NEW(pCookie, XnIntSynchronizerCookie, pSource, pDestination, pConvertFunc); + + nRetVal = m_Cookies.AddFirst(pCookie); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pCookie); + return (nRetVal); + } + + nRetVal = pSource->OnChangeEvent().Register(IntPropertyValueChangedCallback, pCookie, pCookie->hCallback); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pCookie); + m_Cookies.Remove(m_Cookies.Begin()); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + diff --git a/Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.h b/Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.h new file mode 100644 index 0000000..af80456 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnIntPropertySynchronizer.h @@ -0,0 +1,50 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_INT_PROPERTY_SYNCHRONIZER_H__ +#define __XN_INT_PROPERTY_SYNCHRONIZER_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef XnStatus (XN_CALLBACK_TYPE* XnIntPropertyConvertCallback)(XnUInt64 nSourceValue, XnUInt64* pnDestValue); + +class XnIntSynchronizerCookie; // forward declaration + +class XnIntPropertySynchronizer +{ +public: + XnIntPropertySynchronizer(); + ~XnIntPropertySynchronizer(); + + XnStatus RegisterSynchronization(XnIntProperty* pSource, XnIntProperty* pDestination, XnIntPropertyConvertCallback pConvertFunc = NULL); + +private: + typedef xnl::List CookiesList; + CookiesList m_Cookies; +}; + +#endif //__XN_INT_PROPERTY_SYNCHRONIZER_H__ diff --git a/Source/Drivers/PS1080/DDK/XnPixelStream.cpp b/Source/Drivers/PS1080/DDK/XnPixelStream.cpp new file mode 100644 index 0000000..98dee7b --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnPixelStream.cpp @@ -0,0 +1,475 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnPixelStream.h" +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// XnPixelStream +//--------------------------------------------------------------------------- +XnPixelStream::XnPixelStream(const XnChar* csType, const XnChar* csName, XnBool bAllowCustomResolutions) : + XnFrameStream(csType, csName), + m_IsPixelStream(XN_STREAM_PROPERTY_IS_PIXEL_BASED, "IsPixelBased", TRUE), + m_Resolution(XN_STREAM_PROPERTY_RESOLUTION, "Resolution", XN_RESOLUTION_VGA), + m_XRes(XN_STREAM_PROPERTY_X_RES, "XRes", XN_VGA_X_RES), + m_YRes(XN_STREAM_PROPERTY_Y_RES, "YRes", XN_VGA_Y_RES), + m_BytesPerPixel(XN_STREAM_PROPERTY_BYTES_PER_PIXEL, "BytesPerPixel"), + m_Cropping(XN_STREAM_PROPERTY_CROPPING, "Cropping", &m_CroppingData, sizeof(OniCropping), ReadCroppingFromFileCallback), + m_SupportedModesCount(XN_STREAM_PROPERTY_SUPPORT_MODES_COUNT, "SupportedModesCount", 0), + m_SupportedModes(XN_STREAM_PROPERTY_SUPPORT_MODES, "SupportedModes"), + m_supportedModesData(30), + m_bAllowCustomResolutions(bAllowCustomResolutions) +{ + xnOSMemSet(&m_CroppingData, 0, sizeof(OniCropping)); + m_SupportedModes.UpdateGetCallback(GetSupportedModesCallback, this); +} + +XnStatus XnPixelStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnFrameStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + // update set callbacks + m_Resolution.UpdateSetCallback(SetResolutionCallback, this); + m_XRes.UpdateSetCallback(SetXResCallback, this); + m_YRes.UpdateSetCallback(SetYResCallback, this); + m_Cropping.UpdateSetCallback(SetCroppingCallback, this); + + // add properties + XN_VALIDATE_ADD_PROPERTIES(this, &m_IsPixelStream, &m_Resolution, &m_XRes, &m_YRes, + &m_BytesPerPixel, &m_Cropping, &m_SupportedModesCount, &m_SupportedModes); + + // register required size properties + nRetVal = RegisterRequiredSizeProperty(&m_XRes); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = RegisterRequiredSizeProperty(&m_YRes); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = RegisterRequiredSizeProperty(&m_BytesPerPixel); + XN_IS_STATUS_OK(nRetVal); + + // register for important properties + XnCallbackHandle hDummyCallback; + nRetVal = m_Resolution.OnChangeEvent().Register(ResolutionValueChangedCallback, this, hDummyCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = OutputFormatProperty().OnChangeEvent().Register(OutputFormatValueChangedCallback, this, hDummyCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_XRes.OnChangeEvent().Register(FixCroppingCallback, this, hDummyCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_YRes.OnChangeEvent().Register(FixCroppingCallback, this, hDummyCallback); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::AddSupportedModes(XnCmosPreset* aPresets, XnUInt32 nCount) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_supportedModesData.AddLast(aPresets, nCount); + XN_IS_STATUS_OK(nRetVal); + + // update our general property + XnUInt32 nAllPresetsCount = m_supportedModesData.GetSize(); + + nRetVal = m_SupportedModesCount.UnsafeUpdateValue(nAllPresetsCount); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::ValidateSupportedMode(const XnCmosPreset& preset) +{ + for (XnUInt32 i = 0; i < m_supportedModesData.GetSize(); ++i) + { + if (preset.nFormat == m_supportedModesData[i].nFormat && + preset.nResolution == m_supportedModesData[i].nResolution && + preset.nFPS == m_supportedModesData[i].nFPS) + { + return (XN_STATUS_OK); + } + } + + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DDK, "Mode is not supported (format: %d, resolution: %d, FPS: %d)!", preset.nFormat, preset.nResolution, preset.nFPS); +} + +XnStatus XnPixelStream::GetSupportedModes(XnCmosPreset* aPresets, XnUInt32& nCount) +{ + if (nCount < m_supportedModesData.GetSize()) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(aPresets, m_supportedModesData.GetData(), m_supportedModesData.GetSize() * sizeof(XnCmosPreset)); + return XN_STATUS_OK; +} + +XnStatus XnPixelStream::SetResolution(XnResolutions nResolution) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Resolution.UnsafeUpdateValue(nResolution); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::SetXRes(XnUInt32 nXRes) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnResolutions res = XnDDKGetResolutionFromXY(nXRes, GetYRes()); + + // set resolution (this will also set X and Y resolution) + nRetVal = SetResolution(res); + XN_IS_STATUS_OK(nRetVal); + + if (res == XN_RESOLUTION_CUSTOM) + { + // update X res ourselves + nRetVal = m_XRes.UnsafeUpdateValue(nXRes); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::SetYRes(XnUInt32 nYRes) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnResolutions res = XnDDKGetResolutionFromXY(GetXRes(), nYRes); + + // set resolution (this will also set X and Y resolution) + nRetVal = SetResolution(res); + XN_IS_STATUS_OK(nRetVal); + + if (res == XN_RESOLUTION_CUSTOM) + { + // update Y res ourselves + nRetVal = m_YRes.UnsafeUpdateValue(nYRes); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::SetCropping(const OniCropping* pCropping) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = ValidateCropping(pCropping); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Cropping.UnsafeUpdateValue(XN_PACK_GENERAL_BUFFER(*(OniCropping*)pCropping)); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::ValidateCropping(const OniCropping* pCropping) +{ + if (pCropping->enabled) + { + if (pCropping->originX > (int)GetXRes() || + XnUInt32(pCropping->originX + pCropping->width) > GetXRes() || + pCropping->originY > (int)GetYRes() || + XnUInt32(pCropping->originY + pCropping->height) > GetYRes()) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DDK, "Cropping values do not match stream resolution!"); + } + + if (pCropping->width == 0 || pCropping->height == 0) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DDK, "Cannot set a cropping window of zero size!"); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::OnResolutionChanged() +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnResolutions res = (XnResolutions)m_Resolution.GetValue(); + if (res != XN_RESOLUTION_CUSTOM) + { + // update XRes and YRes accordingly + XnUInt32 nXRes; + XnUInt32 nYRes; + if (!XnDDKGetXYFromResolution(res, &nXRes, &nYRes)) + { + XN_ASSERT(FALSE); + } + + nRetVal = m_XRes.UnsafeUpdateValue(nXRes); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_YRes.UnsafeUpdateValue(nYRes); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::OnOutputFormatChanged() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // update the bytes-per-pixel value + XnUInt32 nBytesPerPixel; + + switch (GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_SHIFT_9_2: + nBytesPerPixel = sizeof(XnUInt16); + break; + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + case ONI_PIXEL_FORMAT_DEPTH_100_UM: + nBytesPerPixel = sizeof(OniDepthPixel); + break; + case ONI_PIXEL_FORMAT_GRAY8: + nBytesPerPixel = sizeof(XnUInt8); + break; + case ONI_PIXEL_FORMAT_GRAY16: + nBytesPerPixel = sizeof(XnUInt16); + break; + case ONI_PIXEL_FORMAT_YUV422: + case ONI_PIXEL_FORMAT_YUYV: + // YUV422 is actually 4 bytes for every 2 pixels + nBytesPerPixel = sizeof(XnUChar) * 2; + break; + case ONI_PIXEL_FORMAT_RGB888: + nBytesPerPixel = sizeof(XnUChar) * 3; + break; + case ONI_PIXEL_FORMAT_JPEG: + // size is unknown. + nBytesPerPixel = 1; + break; + default: + return (XN_STATUS_DEVICE_BAD_PARAM); + } + + nRetVal = m_BytesPerPixel.UnsafeUpdateValue(nBytesPerPixel); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::FixCropping() +{ + XnStatus nRetVal = XN_STATUS_OK; + + OniCropping cropping = *GetCropping(); + if (cropping.originX > (int)GetXRes() || + cropping.originY > (int)GetYRes() || + (cropping.originX + cropping.width) > (int)GetXRes() || + (cropping.originY + cropping.height) > (int)GetYRes()) + { + // disable it + cropping.enabled = FALSE; + nRetVal = SetCropping(&cropping); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnPixelStream::CalcRequiredSize(XnUInt32* pnRequiredSize) const +{ + *pnRequiredSize = GetXRes() * GetYRes() * GetBytesPerPixel(); + return XN_STATUS_OK; +} + + +void XnPixelStream::NewDataAvailable(OniFrame* pFrame) +{ + // crop + xnOSEnterCriticalSection(GetLock()); + OniCropping cropping = *GetCropping(); + xnOSLeaveCriticalSection(GetLock()); + + if (cropping.enabled) + { + XnStatus nRetVal = CropImpl(pFrame, &cropping); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_DDK, "Failed to crop! Frame will be dropped"); + return; + } + } + + XnFrameStream::NewDataAvailable(pFrame); +} + +XnStatus XnPixelStream::Mirror(OniFrame* pFrame) const +{ + return XnFormatsMirrorPixelData(GetOutputFormat(), (XnUChar*)pFrame->data, pFrame->dataSize, pFrame->width); +} + +XnStatus XnPixelStream::CropImpl(OniFrame* pFrame, const OniCropping* pCropping) +{ + XnUChar* pPixelData = (XnUChar*)pFrame->data; + XnUInt32 nCurDataSize = 0; + + for (XnUInt32 y = pCropping->originY; y < XnUInt32(pCropping->originY + pCropping->height); ++y) + { + XnUChar* pOrigLine = &pPixelData[y * GetXRes() * GetBytesPerPixel()]; + + // move line + xnOSMemCopy(pPixelData + nCurDataSize, pOrigLine + pCropping->originX * GetBytesPerPixel(), pCropping->width * GetBytesPerPixel()); + nCurDataSize += pCropping->width * GetBytesPerPixel(); + } + + // update size + pFrame->dataSize = nCurDataSize; + + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::ResolutionValueChangedCallback(const XnProperty* /*pSenser*/, void* pCookie) +{ + XnPixelStream* pStream = (XnPixelStream*)pCookie; + return pStream->OnResolutionChanged(); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::OutputFormatValueChangedCallback(const XnProperty* /*pSenser*/, void* pCookie) +{ + XnPixelStream* pStream = (XnPixelStream*)pCookie; + return pStream->OnOutputFormatChanged(); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::FixCroppingCallback(const XnProperty* /*pSenser*/, void* pCookie) +{ + XnPixelStream* pStream = (XnPixelStream*)pCookie; + return pStream->FixCropping(); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::SetResolutionCallback(XnActualIntProperty* /*pSenser*/, XnUInt64 nValue, void* pCookie) +{ + XnPixelStream* pStream = (XnPixelStream*)pCookie; + return pStream->SetResolution((XnResolutions)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::SetXResCallback(XnActualIntProperty* /*pSenser*/, XnUInt64 nValue, void* pCookie) +{ + XnPixelStream* pStream = (XnPixelStream*)pCookie; + return pStream->SetXRes((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::SetYResCallback(XnActualIntProperty* /*pSenser*/, XnUInt64 nValue, void* pCookie) +{ + XnPixelStream* pStream = (XnPixelStream*)pCookie; + return pStream->SetYRes((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::SetCroppingCallback(XnActualGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XnPixelStream* pStream = (XnPixelStream*)pCookie; + if (gbValue.dataSize != sizeof(OniCropping)) + { + return XN_STATUS_DEVICE_PROPERTY_SIZE_DONT_MATCH; + } + + return pStream->SetCropping((OniCropping*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::ReadCroppingFromFileCallback(XnGeneralProperty* pSender, const XnChar* csINIFile, const XnChar* csSection) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // read section name + XnChar csCroppingSection[XN_FILE_MAX_PATH]; + sprintf(csCroppingSection, "%s.Cropping", csSection); + + // read cropping values + XnInt32 nOffsetX; + XnInt32 nOffsetY; + XnInt32 nSizeX; + XnInt32 nSizeY; + XnInt32 bEnabled; + + // only if all values are here + if (XN_STATUS_OK == xnOSReadIntFromINI(csINIFile, csCroppingSection, "OffsetX", &nOffsetX) && + XN_STATUS_OK == xnOSReadIntFromINI(csINIFile, csCroppingSection, "OffsetY", &nOffsetY) && + XN_STATUS_OK == xnOSReadIntFromINI(csINIFile, csCroppingSection, "SizeX", &nSizeX) && + XN_STATUS_OK == xnOSReadIntFromINI(csINIFile, csCroppingSection, "SizeY", &nSizeY) && + XN_STATUS_OK == xnOSReadIntFromINI(csINIFile, csCroppingSection, "Enabled", &bEnabled)) + { + OniCropping Cropping; + Cropping.originX = (XnUInt16)nOffsetX; + Cropping.originY = (XnUInt16)nOffsetY; + Cropping.width = (XnUInt16)nSizeX; + Cropping.height = (XnUInt16)nSizeY; + Cropping.enabled = bEnabled; + + // set value + nRetVal = pSender->SetValue(XN_PACK_GENERAL_BUFFER(Cropping)); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XN_CALLBACK_TYPE XnPixelStream::GetSupportedModesCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XnPixelStream* pThis = (XnPixelStream*)pCookie; + if ((gbValue.dataSize % sizeof(XnCmosPreset)) != 0) + { + return XN_STATUS_INVALID_BUFFER_SIZE; + } + + XnUInt32 nCount = gbValue.dataSize / sizeof(XnCmosPreset); + if (pThis->m_SupportedModesCount.GetValue() != nCount) + { + return XN_STATUS_INVALID_BUFFER_SIZE; + } + + return pThis->GetSupportedModes((XnCmosPreset*)gbValue.data, nCount); +} + +//--------------------------------------------------------------------------- +// XnResolutionProperty +//--------------------------------------------------------------------------- +XnPixelStream::XnResolutionProperty::XnResolutionProperty(XnUInt32 propertyId, const XnChar* strName, XnUInt64 nInitialValue /* = 0 */, const XnChar* strModule /* = "" */) : + XnActualIntProperty(propertyId, strName, nInitialValue, strModule) +{ +} + +XnBool XnPixelStream::XnResolutionProperty::ConvertValueToString(XnChar* csValue, const void* pValue) const +{ + XnUInt64 nValue = *(XnUInt64*)pValue; + strcpy(csValue, XnDDKGetResolutionName((XnResolutions)nValue)); + return TRUE; +} diff --git a/Source/Drivers/PS1080/DDK/XnPixelStream.h b/Source/Drivers/PS1080/DDK/XnPixelStream.h new file mode 100644 index 0000000..3fa24f3 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnPixelStream.h @@ -0,0 +1,138 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PIXEL_STREAM_H__ +#define __XN_PIXEL_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** Represents a stream that is pixel based (meaning, its output data is a matrix of data). */ +class XnPixelStream : public XnFrameStream +{ +public: + XnPixelStream(const XnChar* csType, const XnChar* csName, XnBool bAllowCustomResolutions); + ~XnPixelStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + inline XnResolutions GetResolution() const { return (XnResolutions)m_Resolution.GetValue(); } + inline XnUInt32 GetXRes() const { return (XnUInt32)m_XRes.GetValue(); } + inline XnUInt32 GetYRes() const { return (XnUInt32)m_YRes.GetValue(); } + inline XnUInt32 GetBytesPerPixel() const { return (XnUInt32)m_BytesPerPixel.GetValue(); } + inline const OniCropping* GetCropping() const { return (OniCropping*)m_Cropping.GetValue().data; } + inline const xnl::Array& GetSupportedModes() const { return m_supportedModesData; } + +protected: + XnStatus AddSupportedModes(XnCmosPreset* aPresets, XnUInt32 nCount); + XnStatus ValidateSupportedMode(const XnCmosPreset& preset); + + /** Notifies new data is available in this stream. */ + virtual void NewDataAvailable(OniFrame* pFrame); + + //--------------------------------------------------------------------------- + // Properties Getters + //--------------------------------------------------------------------------- + inline XnActualIntProperty& IsPixelStreamProperty() { return m_IsPixelStream; } + inline XnActualIntProperty& ResolutionProperty() { return m_Resolution; } + inline XnActualIntProperty& XResProperty() { return m_XRes; } + inline XnActualIntProperty& YResProperty() { return m_YRes; } + inline XnActualIntProperty& BytesPerPixelProperty() { return m_BytesPerPixel; } + inline XnActualGeneralProperty& CroppingProperty() { return m_Cropping; } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + virtual XnStatus SetResolution(XnResolutions nResolution); + virtual XnStatus SetXRes(XnUInt32 nXRes); + virtual XnStatus SetYRes(XnUInt32 nYRes); + virtual XnStatus SetCropping(const OniCropping* pCropping); + + //--------------------------------------------------------------------------- + // Virtual Methods + //--------------------------------------------------------------------------- + + /** Crops the stream output. */ + virtual XnStatus CropImpl(OniFrame* pFrame, const OniCropping* pCropping); + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Mirror(OniFrame* pFrame) const; + XnStatus CalcRequiredSize(XnUInt32* pnRequiredSize) const; + + XnStatus ValidateCropping(const OniCropping* pCropping); + +private: + class XnResolutionProperty : public XnActualIntProperty + { + public: + XnResolutionProperty(XnUInt32 propertyId, const XnChar* strName, XnUInt64 nInitialValue = 0, const XnChar* strModule = ""); + XnBool ConvertValueToString(XnChar* csValue, const void* pValue) const; + }; + + XnStatus OnResolutionChanged(); + XnStatus OnOutputFormatChanged(); + XnStatus FixCropping(); + XnStatus GetSupportedModes(XnCmosPreset* aPresets, XnUInt32& nCount); + + static XnStatus XN_CALLBACK_TYPE SetResolutionCallback(XnActualIntProperty* pSenser, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetXResCallback(XnActualIntProperty* pSenser, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetYResCallback(XnActualIntProperty* pSenser, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetCroppingCallback(XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ResolutionValueChangedCallback(const XnProperty* pSenser, void* pCookie); + static XnStatus XN_CALLBACK_TYPE OutputFormatValueChangedCallback(const XnProperty* pSenser, void* pCookie); + static XnStatus XN_CALLBACK_TYPE FixCroppingCallback(const XnProperty* pSenser, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ReadCroppingFromFileCallback(XnGeneralProperty* pSender, const XnChar* csINIFile, const XnChar* csSection); + static XnStatus XN_CALLBACK_TYPE GetSupportedModesCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnActualIntProperty m_IsPixelStream; + XnResolutionProperty m_Resolution; + XnActualIntProperty m_XRes; + XnActualIntProperty m_YRes; + XnActualIntProperty m_BytesPerPixel; + XnActualGeneralProperty m_Cropping; + + OniCropping m_CroppingData; + + XnActualIntProperty m_SupportedModesCount; + XnGeneralProperty m_SupportedModes; + + xnl::Array m_supportedModesData; + XnBool m_bAllowCustomResolutions; +}; + +#endif //__XN_PIXEL_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnProperty.cpp b/Source/Drivers/PS1080/DDK/XnProperty.cpp new file mode 100644 index 0000000..158e3ab --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnProperty.cpp @@ -0,0 +1,193 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnProperty.h" +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnProperty::XnProperty(XnPropertyType Type, void* pValueHolder, XnUInt32 propertyId, const XnChar* strName, const XnChar* strModule) : + m_propertyId(propertyId), + m_Type(Type), + m_pSetCallback(NULL), + m_pSetCallbackCookie(NULL), + m_pGetCallback(NULL), + m_pGetCallbackCookie(NULL), + m_pValueHolder(pValueHolder), + m_LogSeverity(XN_LOG_INFO), + m_bAlwaysSet(FALSE) +{ + UpdateName(strModule, strName); +} + +XnProperty::~XnProperty() +{ +} + +void XnProperty::UpdateName(const XnChar* strModule, const XnChar* strName) +{ + strncpy(m_strModule, strModule, XN_DEVICE_MAX_STRING_LENGTH); + if (m_strName != strName) { + strncpy(m_strName, strName, XN_DEVICE_MAX_STRING_LENGTH); + } +} + +XnStatus XnProperty::SetValue(const void* pValue) +{ + if (m_pSetCallback == NULL) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_PROPERTY_READ_ONLY, XN_MASK_DDK, "Property %s.%s is read only.", GetModule(), GetName()); + } + + if (m_LogSeverity != -1) + { + XnChar strValue[XN_DEVICE_MAX_STRING_LENGTH]; + if (ConvertValueToString(strValue, pValue)) + { + xnLogWrite(XN_MASK_DDK, (XnLogSeverity)m_LogSeverity, __FILE__, __LINE__, "Setting %s.%s to %s...", GetModule(), GetName(), strValue); + } + else + { + xnLogWrite(XN_MASK_DDK, (XnLogSeverity)m_LogSeverity, __FILE__, __LINE__, "Setting %s.%s...", GetModule(), GetName()); + } + } + + if (!m_bAlwaysSet && IsActual() && IsEqual(m_pValueHolder, pValue)) + { + xnLogWrite(XN_MASK_DDK, (XnLogSeverity)m_LogSeverity, __FILE__, __LINE__, "%s.%s value did not change.", GetModule(), GetName()); + } + else + { + XnStatus nRetVal = CallSetCallback(m_pSetCallback, pValue, m_pSetCallbackCookie); + if (nRetVal != XN_STATUS_OK) + { + if (m_LogSeverity != -1) + { + xnLogWrite(XN_MASK_DDK, (XnLogSeverity)m_LogSeverity, __FILE__, __LINE__, "Failed setting %s.%s: %s", GetModule(), GetName(), xnGetStatusString(nRetVal)); + } + return (nRetVal); + } + else + { + xnLogWrite(XN_MASK_DDK, (XnLogSeverity)m_LogSeverity, __FILE__, __LINE__, "%s.%s was successfully set.", GetModule(), GetName()); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnProperty::GetValue(void* pValue) const +{ + if (m_pGetCallback == NULL) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_PROPERTY_WRITE_ONLY, XN_MASK_DDK, "Property %s.%s is write only.", GetModule(), GetName()); + } + + return CallGetCallback(m_pGetCallback, pValue, m_pGetCallbackCookie); +} + +XnStatus XnProperty::UnsafeUpdateValue(const void* pValue /* = NULL */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnBool bValueChanged = TRUE; + + if (IsActual()) + { + if (IsEqual(m_pValueHolder, pValue)) + { + bValueChanged = FALSE; + } + else + { + // update the value + nRetVal = CopyValueImpl(m_pValueHolder, pValue); + XN_IS_STATUS_OK(nRetVal); + } + } + + if (bValueChanged) + { + // print a message + if (m_LogSeverity != -1) + { + XnChar strValue[XN_DEVICE_MAX_STRING_LENGTH]; + XnBool bValueString = FALSE; + + if (IsActual()) + { + bValueString = ConvertValueToString(strValue, pValue); + } + + xnLogWrite(XN_MASK_DDK, (XnLogSeverity)m_LogSeverity, __FILE__, __LINE__, "Property %s.%s was changed%s%s.", GetModule(), GetName(), + bValueString ? " to " : "", bValueString ? strValue : ""); + } + + // raise the event + nRetVal = m_OnChangeEvent.Raise(this); + XN_IS_STATUS_OK(nRetVal); + } + + return XN_STATUS_OK; +} + +void XnProperty::UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) +{ + m_pSetCallback = pFunc; + m_pSetCallbackCookie = pCookie; +} + +void XnProperty::UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) +{ + m_pGetCallback = pFunc; + m_pGetCallbackCookie = pCookie; +} + +XnBool XnProperty::ConvertValueToString(XnChar* /*csValue*/, const void* /*pValue*/) const +{ + return FALSE; +} + +XnStatus XnProperty::ChangeEvent::Raise(const XnProperty* pSender) +{ + XnStatus nRetVal = XN_STATUS_OK; + xnl::AutoCSLocker locker(m_hLock); + ApplyListChanges(); + + for (CallbackPtrList::ConstIterator it = m_callbacks.Begin(); it != m_callbacks.End(); ++it) + { + Callback* pCallback = *it; + nRetVal = pCallback->pFunc(pSender, pCallback->pCookie); + if (nRetVal != XN_STATUS_OK) + { + break; + } + } + + ApplyListChanges(); + return (nRetVal); +} diff --git a/Source/Drivers/PS1080/DDK/XnProperty.h b/Source/Drivers/PS1080/DDK/XnProperty.h new file mode 100644 index 0000000..94bc1e7 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnProperty.h @@ -0,0 +1,144 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PROPERTY_H__ +#define __XN_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** +* A holder for a property (a name and value pair). Note that this class should be inherited, and +* can not be used directly. +*/ +class XnProperty +{ +public: + typedef XnStatus (XN_CALLBACK_TYPE* OnValueChangedHandler)(const XnProperty* pSender, void* pCookie); + typedef xnl::EventInterface ChangeEventInterface; + + /** + * Creates a new property. + * + * @param Type [in] Type of the property. + * @param pValueHolder [in] A pointer to the holder of the value. + * @param strName [in] Name of the property. + * @param strModule [in] Name of the module holding this property. + */ + XnProperty(XnPropertyType Type, void* pValueHolder, XnUInt32 propertyId, const XnChar* strName, const XnChar* strModule); + virtual ~XnProperty(); + + inline XnUInt32 GetId() const { return m_propertyId; } + inline const XnChar* GetName() const { return m_strName; } + inline const XnChar* GetModule() const { return m_strModule; } + inline XnBool IsActual() const { return (m_pValueHolder != NULL); } + inline XnBool IsReadOnly() const { return (m_pGetCallback == NULL); } + inline XnPropertyType GetType() const { return m_Type; } + + inline ChangeEventInterface& OnChangeEvent() { return m_OnChangeEvent; } + + /** Updates property name. */ + void UpdateName(const XnChar* strModule, const XnChar* strName); + + /** Updates the value of the property according to an INI file. */ + virtual XnStatus ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection) = 0; + + /** Adds this property to the property set. */ + virtual XnStatus AddToPropertySet(XnPropertySet* pSet) = 0; + + /** Sets the log severity under which changes to the property are printed. */ + inline void SetLogSeverity(XnInt32 nSeverity) { m_LogSeverity = nSeverity; } + + /** When TRUE, the property will always call the set callback, even if value hasn't changed. */ + inline void SetAlwaysSet(XnBool bAlwaysSet) { m_bAlwaysSet = bAlwaysSet; } + +protected: + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnProperty* pSender, const void* pValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnProperty* pSender, void* pValue, void* pCookie); + + /** Sets the property value. */ + XnStatus SetValue(const void* pValue); + + /** Gets the property value. */ + XnStatus GetValue(void* pValue) const; + + /** Updates the value of the property without any checks. */ + XnStatus UnsafeUpdateValue(const void* pValue = NULL); + + /** Updates the set callback. */ + void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie); + + /** Updates the get callback. */ + void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie); + + virtual XnStatus CopyValueImpl(void* pDest, const void* pSource) const = 0; + virtual XnBool IsEqual(const void* pValue1, const void* pValue2) const = 0; + virtual XnStatus CallSetCallback(SetFuncPtr pFunc, const void* pValue, void* pCookie) = 0; + virtual XnStatus CallGetCallback(GetFuncPtr pFunc, void* pValue, void* pCookie) const = 0; + virtual XnBool ConvertValueToString(XnChar* csValue, const void* pValue) const; + + inline void* Value() const { return m_pValueHolder; } + +private: + class ChangeEvent : public xnl::EventInterface + { + public: + XnStatus Raise(const XnProperty* pSender); + }; + + XnChar m_strModule[XN_DEVICE_MAX_STRING_LENGTH]; // module name + XnChar m_strName[XN_DEVICE_MAX_STRING_LENGTH]; // property name + XnUInt32 m_propertyId; + XnPropertyType m_Type; // property type + + // Set callback + SetFuncPtr m_pSetCallback; + void* m_pSetCallbackCookie; + + // Get callback + GetFuncPtr m_pGetCallback; + void* m_pGetCallbackCookie; + + void* m_pValueHolder; // a pointer to the storage of the property + + ChangeEvent m_OnChangeEvent; + + XnInt32 m_LogSeverity; + XnBool m_bAlwaysSet; +}; + +/** A property list */ +typedef xnl::List XnPropertiesList; + +/** A hash table, mapping property name to the property */ +typedef xnl::Hash XnPropertiesHash; + +#endif //__XN_PROPERTY_H__ diff --git a/Source/Drivers/PS1080/DDK/XnPropertySet.cpp b/Source/Drivers/PS1080/DDK/XnPropertySet.cpp new file mode 100644 index 0000000..9ed0e8f --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnPropertySet.cpp @@ -0,0 +1,668 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnPropertySetInternal.h" +#include "XnActualIntProperty.h" +#include "XnActualRealProperty.h" +#include "XnActualStringProperty.h" +#include "XnActualGeneralProperty.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +struct XnPropertySetModuleEnumerator +{ + XnBool bFirst; + XnPropertySetData* pModules; + XnPropertySetData::ConstIterator it; +}; + +struct XnPropertySetEnumerator +{ + XnBool bFirst; + XnPropertySetData* pModules; + XnPropertySetData::ConstIterator itModule; + XnChar strModule[XN_DEVICE_MAX_STRING_LENGTH]; + XnActualPropertiesHash::ConstIterator itProp; +}; + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnStatus XnPropertySetCreate(XnPropertySet** ppSet) +{ + XN_VALIDATE_OUTPUT_PTR(ppSet); + + XnPropertySet* pSet; + XN_VALIDATE_ALLOC(pSet, XnPropertySet); + + pSet->pData = XN_NEW(XnPropertySetData); + if (pSet->pData == NULL) + { + xnOSFree(pSet); + return XN_STATUS_ALLOC_FAILED; + } + + *ppSet = pSet; + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetDestroy(XnPropertySet** ppSet) +{ + XN_VALIDATE_INPUT_PTR(ppSet); + XN_VALIDATE_INPUT_PTR(*ppSet); + + XnPropertySet* pSet = (*ppSet); + + if (pSet->pData != NULL) + { + XnPropertySetClear(pSet); + XN_DELETE(pSet->pData); + } + + xnOSFree(pSet); + + *ppSet = NULL; + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetClear(XnPropertySet* pSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + + while (!pSet->pData->IsEmpty()) + { + nRetVal = XnPropertySetRemoveModule(pSet, pSet->pData->Begin()->Key()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetAddModule(XnPropertySet* pSet, const XnChar* strModuleName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModuleName); + + XnActualPropertiesHash* pModule = NULL; + + // make sure this module doesn't already exists + nRetVal = pSet->pData->Get(strModuleName, pModule); + if (XN_STATUS_OK == nRetVal) + { + return XN_STATUS_DEVICE_MODULE_ALREADY_EXISTS; + } + + // doesn't exist. create a new one + XN_VALIDATE_NEW(pModule, XnActualPropertiesHash, strModuleName); + + nRetVal = XnPropertySetDataAttachModule(pSet->pData, strModuleName, pModule); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pModule); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetRemoveModule(XnPropertySet* pSet, const XnChar* strModuleName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModuleName); + + XnActualPropertiesHash* pModule = NULL; + nRetVal = XnPropertySetDataDetachModule(pSet->pData, strModuleName, &pModule); + XN_IS_STATUS_OK(nRetVal); + + // now free its memory + XN_DELETE(pModule); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetAddIntProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModuleName); + + // get module + XnActualPropertiesHash* pModule = NULL; + nRetVal = pSet->pData->Get(strModuleName, pModule); + XN_IS_STATUS_OK(nRetVal); + + // add property + nRetVal = pModule->Add(propertyId, "", nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetAddRealProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, XnDouble dValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModuleName); + + // get module + XnActualPropertiesHash* pModule = NULL; + nRetVal = pSet->pData->Get(strModuleName, pModule); + XN_IS_STATUS_OK(nRetVal); + + // add property + nRetVal = pModule->Add(propertyId, "", dValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetAddStringProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, const XnChar* strValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModuleName); + XN_VALIDATE_INPUT_PTR(strValue); + + // get module + XnActualPropertiesHash* pModule = NULL; + nRetVal = pSet->pData->Get(strModuleName, pModule); + XN_IS_STATUS_OK(nRetVal); + + // add property + nRetVal = pModule->Add(propertyId, "", strValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetAddGeneralProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, const OniGeneralBuffer* pgbValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModuleName); + XN_VALIDATE_INPUT_PTR(pgbValue); + + // get module + XnActualPropertiesHash* pModule = NULL; + nRetVal = pSet->pData->Get(strModuleName, pModule); + XN_IS_STATUS_OK(nRetVal); + + // add property + nRetVal = pModule->Add(propertyId, "", *pgbValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetRemoveProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModuleName); + + // get module + XnActualPropertiesHash* pModule = NULL; + nRetVal = pSet->pData->Get(strModuleName, pModule); + XN_IS_STATUS_OK(nRetVal); + + // remove the property + nRetVal = pModule->Remove(propertyId); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetGetModuleEnumerator(const XnPropertySet* pSet, XnPropertySetModuleEnumerator** ppEnumerator) +{ + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_OUTPUT_PTR(ppEnumerator); + + XnPropertySetModuleEnumerator* pEnumer; + XN_VALIDATE_NEW(pEnumer, XnPropertySetModuleEnumerator); + + pEnumer->bFirst = TRUE; + pEnumer->it = pSet->pData->End(); + pEnumer->pModules = pSet->pData; + + *ppEnumerator = pEnumer; + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetModuleEnumeratorFree(XnPropertySetModuleEnumerator** ppEnumer) +{ + XN_VALIDATE_INPUT_PTR(ppEnumer); + XN_VALIDATE_INPUT_PTR(*ppEnumer); + + XN_DELETE(*ppEnumer); + *ppEnumer = NULL; + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetModuleEnumeratorMoveNext(XnPropertySetModuleEnumerator* pEnumerator, XnBool* pbEnd) +{ + XN_VALIDATE_INPUT_PTR(pEnumerator); + XN_VALIDATE_OUTPUT_PTR(pbEnd); + + if (pEnumerator->bFirst) + { + pEnumerator->it = pEnumerator->pModules->Begin(); + pEnumerator->bFirst = FALSE; + } + else if (pEnumerator->it == pEnumerator->pModules->End()) + { + return XN_STATUS_ILLEGAL_POSITION; + } + else + { + pEnumerator->it++; + } + + *pbEnd = (pEnumerator->it == pEnumerator->pModules->End()); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetModuleEnumeratorGetCurrent(const XnPropertySetModuleEnumerator* pEnumer, const XnChar** pstrModuleName) +{ + XN_VALIDATE_INPUT_PTR(pEnumer); + XN_VALIDATE_OUTPUT_PTR(pstrModuleName); + + if (pEnumer->it == pEnumer->pModules->End()) + { + return XN_STATUS_ILLEGAL_POSITION; + } + + *pstrModuleName = pEnumer->it->Key(); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetGetEnumerator(const XnPropertySet* pSet, XnPropertySetEnumerator** ppEnumerator, const XnChar* strModule /* = NULL */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_OUTPUT_PTR(ppEnumerator); + + if (strModule != NULL) + { + // make sure module exists + XnPropertySetData::ConstIterator it = pSet->pData->End(); + nRetVal = pSet->pData->Find(strModule, it); + XN_IS_STATUS_OK(nRetVal); + } + + XnPropertySetEnumerator* pEnumer; + XN_VALIDATE_NEW(pEnumer, XnPropertySetEnumerator) + + pEnumer->bFirst = TRUE; + pEnumer->pModules = pSet->pData; + if (strModule != NULL) + { + strncpy(pEnumer->strModule, strModule, XN_DEVICE_MAX_STRING_LENGTH); + } + else + { + pEnumer->strModule[0] = '\0'; + } + + *ppEnumerator = pEnumer; + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetFindProperty(const XnPropertySet* pSet, const XnChar* strModule, XnUInt32 propertyId, XnPropertySetEnumerator** ppEnumerator) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSet); + XN_VALIDATE_INPUT_PTR(strModule); + XN_VALIDATE_OUTPUT_PTR(ppEnumerator); + + // find module + XnPropertySetData::Iterator itModule = pSet->pData->End(); + nRetVal = pSet->pData->Find(strModule, itModule); + XN_IS_STATUS_OK(nRetVal); + + XnActualPropertiesHash* pModule = itModule->Value(); + + // find property + XnActualPropertiesHash::Iterator itProp = pModule->End(); + nRetVal = pModule->Find(propertyId, itProp); + XN_IS_STATUS_OK(nRetVal); + + // create enumerator + XnPropertySetEnumerator* pEnumer; + XN_VALIDATE_NEW(pEnumer, XnPropertySetEnumerator); + + pEnumer->itModule = itModule; + pEnumer->itProp = itProp; + pEnumer->pModules = pSet->pData; + pEnumer->strModule[0] = '\0'; + pEnumer->bFirst = FALSE; + + *ppEnumerator = pEnumer; + + return XN_STATUS_OK; +} + +XnStatus XnPropertySetEnumeratorFree(XnPropertySetEnumerator** ppEnumerator) +{ + XN_VALIDATE_INPUT_PTR(ppEnumerator); + XN_VALIDATE_INPUT_PTR(*ppEnumerator); + + XN_DELETE(*ppEnumerator); + *ppEnumerator = NULL; + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetEnumeratorMoveNext(XnPropertySetEnumerator* pEnumerator, XnBool* pbEnd) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pEnumerator); + XN_VALIDATE_OUTPUT_PTR(pbEnd); + + *pbEnd = TRUE; + + if (pEnumerator->strModule[0] != '\0') // single module + { + if (pEnumerator->bFirst) + { + pEnumerator->bFirst = FALSE; + + // find this module + nRetVal = pEnumerator->pModules->Find(pEnumerator->strModule, pEnumerator->itModule); + if (nRetVal == XN_STATUS_NO_MATCH) + { + pEnumerator->itModule = pEnumerator->pModules->End(); + } + XN_IS_STATUS_OK(nRetVal); + + pEnumerator->itProp = pEnumerator->itModule->Value()->Begin(); + } + else if (pEnumerator->itProp == pEnumerator->itModule->Value()->End()) + { + return XN_STATUS_ILLEGAL_POSITION; + } + else + { + // advance prop iterator + ++pEnumerator->itProp; + } + + *pbEnd = (pEnumerator->itProp == pEnumerator->itModule->Value()->End()); + } + else // all modules + { + if (pEnumerator->bFirst) + { + pEnumerator->bFirst = FALSE; + + // search for the first modules that has properties + pEnumerator->itModule = pEnumerator->pModules->Begin(); + while (pEnumerator->itModule != pEnumerator->pModules->End() && pEnumerator->itModule->Value()->IsEmpty()) + { + pEnumerator->itModule++; + } + + // if we found one, take it's first property + if (pEnumerator->itModule != pEnumerator->pModules->End()) + { + pEnumerator->itProp = pEnumerator->itModule->Value()->Begin(); + *pbEnd = FALSE; + } + else + { + *pbEnd = TRUE; + } + } + else if (pEnumerator->itModule == pEnumerator->pModules->End()) + { + return XN_STATUS_ILLEGAL_POSITION; + } + else + { + // move to next one + ++pEnumerator->itProp; + + // check if we reached end of module + if (pEnumerator->itProp == pEnumerator->itModule->Value()->End()) + { + // move to next module with properties + do + { + pEnumerator->itModule++; + } + while (pEnumerator->itModule != pEnumerator->pModules->End() && pEnumerator->itModule->Value()->IsEmpty()); + + // if we found one, take it's first property + if (pEnumerator->itModule != pEnumerator->pModules->End()) + { + pEnumerator->itProp = pEnumerator->itModule->Value()->Begin(); + *pbEnd = FALSE; + } + else + { + *pbEnd = TRUE; + } + } + else + { + *pbEnd = FALSE; + } + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetEnumeratorGetCurrentPropertyInfo(const XnPropertySetEnumerator* pEnumerator, XnPropertyType* pnType, const XnChar** pstrModule, const XnChar** pstrProp) +{ + XN_VALIDATE_INPUT_PTR(pEnumerator); + XN_VALIDATE_OUTPUT_PTR(pnType); + XN_VALIDATE_OUTPUT_PTR(pstrModule); + XN_VALIDATE_OUTPUT_PTR(pstrProp); + + XnProperty* pProp = pEnumerator->itProp->Value(); + *pnType = pProp->GetType(); + *pstrModule = pProp->GetModule(); + *pstrProp = pProp->GetName(); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetEnumeratorGetIntValue(const XnPropertySetEnumerator* pEnumerator, XnUInt64* pnValue) +{ + XN_VALIDATE_INPUT_PTR(pEnumerator); + XN_VALIDATE_OUTPUT_PTR(pnValue); + + XnProperty* pPropBase = pEnumerator->itProp->Value(); + if (pPropBase->GetType() != XN_PROPERTY_TYPE_INTEGER) + { + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + XnActualIntProperty* pProp = (XnActualIntProperty*)pPropBase; + *pnValue = pProp->GetValue(); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetEnumeratorGetRealValue(const XnPropertySetEnumerator* pEnumerator, XnDouble* pdValue) +{ + XN_VALIDATE_INPUT_PTR(pEnumerator); + XN_VALIDATE_OUTPUT_PTR(pdValue); + + XnProperty* pPropBase = pEnumerator->itProp->Value(); + if (pPropBase->GetType() != XN_PROPERTY_TYPE_REAL) + { + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + XnActualRealProperty* pProp = (XnActualRealProperty*)pPropBase; + *pdValue = pProp->GetValue(); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetEnumeratorGetStringValue(const XnPropertySetEnumerator* pEnumerator, const XnChar** pstrValue) +{ + XN_VALIDATE_INPUT_PTR(pEnumerator); + XN_VALIDATE_OUTPUT_PTR(pstrValue); + + XnProperty* pPropBase = pEnumerator->itProp->Value(); + if (pPropBase->GetType() != XN_PROPERTY_TYPE_STRING) + { + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + XnActualStringProperty* pProp = (XnActualStringProperty*)pPropBase; + *pstrValue = pProp->GetValue(); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetEnumeratorGetGeneralValue(const XnPropertySetEnumerator* pEnumerator, OniGeneralBuffer* pgbValue) +{ + XN_VALIDATE_INPUT_PTR(pEnumerator); + XN_VALIDATE_OUTPUT_PTR(pgbValue); + + XnProperty* pPropBase = pEnumerator->itProp->Value(); + if (pPropBase->GetType() != XN_PROPERTY_TYPE_GENERAL) + { + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + XnActualGeneralProperty* pProp = (XnActualGeneralProperty*)pPropBase; + *pgbValue = pProp->GetValue(); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetDataAttachModule(XnPropertySetData* pSetData, const XnChar* strModuleName, XnActualPropertiesHash* pModule) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSetData); + XN_VALIDATE_INPUT_PTR(strModuleName); + XN_VALIDATE_INPUT_PTR(pModule); + + nRetVal = pSetData->Set(strModuleName, pModule); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetDataDetachModule(XnPropertySetData* pSetData, const XnChar* strModuleName, XnActualPropertiesHash** ppModule) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pSetData); + XN_VALIDATE_INPUT_PTR(strModuleName); + XN_VALIDATE_OUTPUT_PTR(ppModule); + + // remove it + XnPropertySetDataInternal::Iterator it = pSetData->Find(strModuleName); + if (it == pSetData->End()) + { + return XN_STATUS_NO_MATCH; + } + + *ppModule = it->Value(); + + nRetVal = pSetData->Remove(strModuleName); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnPropertySetCloneModule(const XnPropertySet* pSource, XnPropertySet* pDest, const XnChar* strModule, const XnChar* strNewName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnActualPropertiesHash* pModuleProps = NULL; + nRetVal = pSource->pData->Get(strModule, pModuleProps); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnPropertySetAddModule(pDest, strNewName); + XN_IS_STATUS_OK(nRetVal); + + for (XnActualPropertiesHash::ConstIterator it = pModuleProps->Begin(); it != pModuleProps->End(); ++it) + { + XnProperty* pProp = it->Value(); + switch (pProp->GetType()) + { + case XN_PROPERTY_TYPE_INTEGER: + { + XnActualIntProperty* pIntProp = (XnActualIntProperty*)pProp; + nRetVal = XnPropertySetAddIntProperty(pDest, strNewName, pIntProp->GetId(), pIntProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_REAL: + { + XnActualRealProperty* pRealProp = (XnActualRealProperty*)pProp; + nRetVal = XnPropertySetAddRealProperty(pDest, strNewName, pRealProp->GetId(), pRealProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_STRING: + { + XnActualStringProperty* pStrProp = (XnActualStringProperty*)pProp; + nRetVal = XnPropertySetAddStringProperty(pDest, strNewName, pStrProp->GetId(), pStrProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + case XN_PROPERTY_TYPE_GENERAL: + { + XnActualGeneralProperty* pGenProp = (XnActualGeneralProperty*)pProp; + nRetVal = XnPropertySetAddGeneralProperty(pDest, strNewName, pGenProp->GetId(), &pGenProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + break; + } + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DDK, "Unknown property type: %d", pProp->GetType()); + } + } + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/DDK/XnPropertySetInternal.h b/Source/Drivers/PS1080/DDK/XnPropertySetInternal.h new file mode 100644 index 0000000..9ee82c6 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnPropertySetInternal.h @@ -0,0 +1,58 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PROPERTY_SET_INTERNAL_H__ +#define __XN_PROPERTY_SET_INTERNAL_H__ + +#include +#include "XnActualPropertiesHash.h" + +typedef XnStringsHashT XnPropertySetDataInternal; + +class XnPropertySetData; + +struct XnPropertySet +{ + XnPropertySetData* pData; +}; + +class XnPropertySetData : public XnPropertySetDataInternal +{ +public: + ~XnPropertySetData() + { + XnPropertySet set; + set.pData = this; + XnPropertySetClear(&set); + } +}; + +#define _XN_PROPERTY_SET_NAME(name) __ ## name ## _ ## Data + +#define XN_PROPERTY_SET_CREATE_ON_STACK(name) \ + XnPropertySetData _XN_PROPERTY_SET_NAME(name); \ + XnPropertySet name; \ + name.pData = &_XN_PROPERTY_SET_NAME(name); + +XnStatus XnPropertySetDataAttachModule(XnPropertySetData* pSetData, const XnChar* strModuleName, XnActualPropertiesHash* pModule); +XnStatus XnPropertySetDataDetachModule(XnPropertySetData* pSetData, const XnChar* strModuleName, XnActualPropertiesHash** ppModule); +XnStatus XnPropertySetCloneModule(const XnPropertySet* pSource, XnPropertySet* pDest, const XnChar* strModule, const XnChar* strNewName); + +#endif //__XN_PROPERTY_SET_INTERNAL_H__ diff --git a/Source/Drivers/PS1080/DDK/XnRealProperty.cpp b/Source/Drivers/PS1080/DDK/XnRealProperty.cpp new file mode 100644 index 0000000..e7fb938 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnRealProperty.cpp @@ -0,0 +1,93 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnRealProperty.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnRealProperty::XnRealProperty(XnUInt32 propertyId, const XnChar* strName, XnDouble* pValueHolder, const XnChar* strModule /* = "" */) : + XnProperty(XN_PROPERTY_TYPE_REAL, pValueHolder, propertyId, strName, strModule) +{ +} + +XnStatus XnRealProperty::ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDouble dValue; + + nRetVal = xnOSReadDoubleFromINI(csINIFile, csSection, GetName(), &dValue); + if (nRetVal == XN_STATUS_OK) + { + nRetVal = SetValue(dValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnRealProperty::CopyValueImpl(void* pDest, const void* pSource) const +{ + (*(XnDouble*)pDest) = (*(const XnDouble*)pSource); + return XN_STATUS_OK; +} + +XnBool XnRealProperty::IsEqual(const void* pValue1, const void* pValue2) const +{ + return (*(XnDouble*)pValue1) == (*(XnDouble*)pValue2); +} + +XnStatus XnRealProperty::CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie) +{ + SetFuncPtr pCallback = (SetFuncPtr)pFunc; + return pCallback(this, *(const XnDouble*)pValue, pCookie); +} + +XnStatus XnRealProperty::CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const +{ + GetFuncPtr pCallback = (GetFuncPtr)pFunc; + return pCallback(this, (XnDouble*)pValue, pCookie); +} + +XnBool XnRealProperty::ConvertValueToString(XnChar* csValue, const void* pValue) const +{ + sprintf(csValue, "%f", *(XnDouble*)pValue); + return TRUE; +} + +XnStatus XnRealProperty::AddToPropertySet(XnPropertySet* pSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDouble dValue; + nRetVal = GetValue(&dValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnPropertySetAddRealProperty(pSet, GetModule(), GetId(), dValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/DDK/XnRealProperty.h b/Source/Drivers/PS1080/DDK/XnRealProperty.h new file mode 100644 index 0000000..9b25daa --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnRealProperty.h @@ -0,0 +1,85 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_REAL_PROPERTY_H__ +#define __XN_REAL_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Class +//--------------------------------------------------------------------------- + +/** +* A property of type integer. +*/ +class XnRealProperty : public XnProperty +{ +public: + XnRealProperty(XnUInt32 propertyId, const XnChar* strName, XnDouble* pValueHolder, const XnChar* strModule = ""); + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnRealProperty* pSender, XnDouble dValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnRealProperty* pSender, XnDouble* pdValue, void* pCookie); + + inline XnStatus SetValue(XnDouble dValue) + { + return XnProperty::SetValue(&dValue); + } + + inline XnStatus GetValue(XnDouble* pdValue) const + { + XN_VALIDATE_OUTPUT_PTR(pdValue); + return XnProperty::GetValue(pdValue); + } + + inline XnStatus UnsafeUpdateValue(XnDouble dValue) + { + return XnProperty::UnsafeUpdateValue(&dValue); + } + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateSetCallback((XnProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateGetCallback((XnProperty::GetFuncPtr)pFunc, pCookie); + } + + virtual XnStatus ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection); + + XnStatus AddToPropertySet(XnPropertySet* pSet); + +protected: + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + virtual XnStatus CopyValueImpl(void* pDest, const void* pSource) const; + virtual XnBool IsEqual(const void* pValue1, const void* pValue2) const; + virtual XnStatus CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie); + virtual XnStatus CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const; + virtual XnBool ConvertValueToString(XnChar* csValue, const void* pValue) const; +}; + +#endif //__XN_REAL_PROPERTY_H__ diff --git a/Source/Drivers/PS1080/DDK/XnShiftToDepth.cpp b/Source/Drivers/PS1080/DDK/XnShiftToDepth.cpp new file mode 100644 index 0000000..643ace5 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnShiftToDepth.cpp @@ -0,0 +1,145 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnShiftToDepth.h" +#include +#include "XnDDK.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnShiftToDepthInit(XnShiftToDepthTables* pShiftToDepth, const XnShiftToDepthConfig* pConfig) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + XN_VALIDATE_INPUT_PTR(pConfig); + + XN_VALIDATE_ALIGNED_CALLOC(pShiftToDepth->pShiftToDepthTable, OniDepthPixel, pConfig->nDeviceMaxShiftValue+1, XN_DEFAULT_MEM_ALIGN); + XN_VALIDATE_ALIGNED_CALLOC(pShiftToDepth->pDepthToShiftTable, XnUInt16, pConfig->nDeviceMaxDepthValue+1, XN_DEFAULT_MEM_ALIGN); + pShiftToDepth->bIsInitialized = TRUE; + + // store allocation sizes + pShiftToDepth->nShiftsCount = pConfig->nDeviceMaxShiftValue + 1; + pShiftToDepth->nDepthsCount = pConfig->nDeviceMaxDepthValue + 1; + + return XnShiftToDepthUpdate(pShiftToDepth, pConfig); +} + +XnStatus XnShiftToDepthUpdate(XnShiftToDepthTables* pShiftToDepth, const XnShiftToDepthConfig* pConfig) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + XN_VALIDATE_INPUT_PTR(pConfig); + + // check max shift wasn't changed (if so, memory should be re-allocated) + if (pConfig->nDeviceMaxShiftValue > pShiftToDepth->nShiftsCount) + return XN_STATUS_DEVICE_INVALID_MAX_SHIFT; + + // check max depth wasn't changed (if so, memory should be re-allocated) + if (pConfig->nDeviceMaxDepthValue > pShiftToDepth->nDepthsCount) + return XN_STATUS_DEVICE_INVALID_MAX_DEPTH; + + XnUInt32 nIndex = 0; + XnInt16 nShiftValue = 0; + XnDouble dFixedRefX = 0; + XnDouble dMetric = 0; + XnDouble dDepth = 0; + XnDouble dPlanePixelSize = pConfig->fZeroPlanePixelSize; + XnDouble dPlaneDsr = pConfig->nZeroPlaneDistance; + XnDouble dPlaneDcl = pConfig->fEmitterDCmosDistance; + XnInt32 nConstShift = pConfig->nParamCoeff * pConfig->nConstShift; + + dPlanePixelSize *= pConfig->nPixelSizeFactor; + nConstShift /= pConfig->nPixelSizeFactor; + + OniDepthPixel* pShiftToDepthTable = pShiftToDepth->pShiftToDepthTable; + XnUInt16* pDepthToShiftTable = pShiftToDepth->pDepthToShiftTable; + + xnOSMemSet(pShiftToDepthTable, 0, pShiftToDepth->nShiftsCount * sizeof(OniDepthPixel)); + xnOSMemSet(pDepthToShiftTable, 0, pShiftToDepth->nDepthsCount * sizeof(XnUInt16)); + + XnUInt16 nLastDepth = 0; + XnUInt16 nLastIndex = 0; + + XnUInt32 nMaxDepth = XN_MIN(pConfig->nDeviceMaxDepthValue, pConfig->nDepthMaxCutOff); + + for (nIndex = 1; nIndex < pConfig->nDeviceMaxShiftValue; nIndex++) + { + nShiftValue = (XnInt16)nIndex; + + dFixedRefX = (XnDouble)(nShiftValue - nConstShift) / (XnDouble)pConfig->nParamCoeff; + dFixedRefX -= 0.375; + dMetric = dFixedRefX * dPlanePixelSize; + dDepth = pConfig->nShiftScale * ((dMetric * dPlaneDsr / (dPlaneDcl - dMetric)) + dPlaneDsr); + + // check cut-offs + if ((dDepth > pConfig->nDepthMinCutOff) && (dDepth < nMaxDepth)) + { + pShiftToDepthTable[nIndex] = (XnUInt16)dDepth; + + for (XnUInt16 i = nLastDepth; i < dDepth; i++) + pDepthToShiftTable[i] = nLastIndex; + + nLastIndex = (XnUInt16)nIndex; + nLastDepth = (XnUInt16)dDepth; + } + } + + for (XnUInt16 i = nLastDepth; i <= pConfig->nDeviceMaxDepthValue; i++) + pDepthToShiftTable[i] = nLastIndex; + + return XN_STATUS_OK; +} + +XnStatus XnShiftToDepthConvert(XnShiftToDepthTables* pShiftToDepth, XnUInt16* pInput, XnUInt32 nInputSize, OniDepthPixel* pOutput) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + + XnUInt16* pInputEnd = pInput + nInputSize; + OniDepthPixel* pShiftToDepthTable = pShiftToDepth->pShiftToDepthTable; + + while (pInput != pInputEnd) + { + pOutput[0] = pShiftToDepthTable[pInput[0]]; + + pInput++; + pOutput++; + } + + return XN_STATUS_OK; +} + +XnStatus XnShiftToDepthFree(XnShiftToDepthTables* pShiftToDepth) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + + if (pShiftToDepth->bIsInitialized) + { + XN_ALIGNED_FREE_AND_NULL(pShiftToDepth->pDepthToShiftTable); + XN_ALIGNED_FREE_AND_NULL(pShiftToDepth->pShiftToDepthTable); + pShiftToDepth->bIsInitialized = FALSE; + } + + return XN_STATUS_OK; +} + diff --git a/Source/Drivers/PS1080/DDK/XnShiftToDepth.h b/Source/Drivers/PS1080/DDK/XnShiftToDepth.h new file mode 100644 index 0000000..6807110 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnShiftToDepth.h @@ -0,0 +1,78 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_SHIFT_TO_DEPTH_H_ +#define _XN_SHIFT_TO_DEPTH_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef struct XnShiftToDepthConfig +{ + /** The zero plane distance in depth units. */ + OniDepthPixel nZeroPlaneDistance; + /** The zero plane pixel size */ + XnFloat fZeroPlanePixelSize; + /** The distance between the emitter and the Depth Cmos */ + XnFloat fEmitterDCmosDistance; + /** The maximum possible shift value from this device. */ + XnUInt32 nDeviceMaxShiftValue; + /** The maximum possible depth from this device (as opposed to a cut-off). */ + XnUInt32 nDeviceMaxDepthValue; + + XnUInt32 nConstShift; + XnUInt32 nPixelSizeFactor; + XnUInt32 nParamCoeff; + XnUInt32 nShiftScale; + + OniDepthPixel nDepthMinCutOff; + OniDepthPixel nDepthMaxCutOff; + +} XnShiftToDepthConfig; + +typedef struct XnShiftToDepthTables +{ + XnBool bIsInitialized; + /** The shift-to-depth table. */ + OniDepthPixel* pShiftToDepthTable; + /** The number of entries in the shift-to-depth table. */ + XnUInt32 nShiftsCount; + /** The depth-to-shift table. */ + XnUInt16* pDepthToShiftTable; + /** The number of entries in the depth-to-shift table. */ + XnUInt32 nDepthsCount; +} XnShiftToDepthTables; + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +XnStatus XnShiftToDepthInit(XnShiftToDepthTables* pShiftToDepth, const XnShiftToDepthConfig* pConfig); +XnStatus XnShiftToDepthUpdate(XnShiftToDepthTables* pShiftToDepth, const XnShiftToDepthConfig* pConfig); +XnStatus XnShiftToDepthConvert(XnShiftToDepthTables* pShiftToDepth, XnUInt16* pInput, XnUInt32 nInputSize, OniDepthPixel* pOutput); +XnStatus XnShiftToDepthFree(XnShiftToDepthTables* pShiftToDepth); + +#endif //_XN_SHIFT_TO_DEPTH_H_ diff --git a/Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.cpp b/Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.cpp new file mode 100644 index 0000000..3633549 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.cpp @@ -0,0 +1,327 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "XnShiftToDepthStreamHelper.h" +#include + +XnShiftToDepthStreamHelper::XnShiftToDepthStreamHelper() : + m_ShiftToDepthTable(XN_STREAM_PROPERTY_S2D_TABLE, "S2D", NULL, 0, NULL), + m_DepthToShiftTable(XN_STREAM_PROPERTY_D2S_TABLE, "D2S", NULL, 0, NULL), + m_pModule(NULL), + m_bPropertiesAdded(FALSE) +{ + m_ShiftToDepthTable.UpdateGetCallback(GetShiftToDepthTableCallback, this); + m_DepthToShiftTable.UpdateGetCallback(GetDepthToShiftTableCallback, this); + xnOSMemSet(&m_ShiftToDepthTables, 0, sizeof(XnShiftToDepthTables)); +} + +XnShiftToDepthStreamHelper::~XnShiftToDepthStreamHelper() +{ + XnShiftToDepthStreamHelper::Free(); +} + +XnStatus XnShiftToDepthStreamHelper::Init(XnDeviceModule* pModule) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pModule); + m_pModule = pModule; + + // old depth streams did not have S2D tables as actual properties. Add these properties + XnBool bDoesExist = FALSE; + nRetVal = m_pModule->DoesPropertyExist(XN_STREAM_PROPERTY_S2D_TABLE, &bDoesExist); + XN_IS_STATUS_OK(nRetVal); + + if (!bDoesExist) + { + // add properties to the module + XN_VALIDATE_ADD_PROPERTIES(m_pModule, &m_ShiftToDepthTable, &m_DepthToShiftTable); + + m_bPropertiesAdded = TRUE; + + // now create tables and register to properties + nRetVal = InitShiftToDepth(); + XN_IS_STATUS_OK(nRetVal); + } + else + { + m_ShiftToDepthTables.pShiftToDepthTable = (OniDepthPixel*)m_ShiftToDepthTable.GetValue().data; + m_ShiftToDepthTables.pDepthToShiftTable = (XnUInt16*)m_DepthToShiftTable.GetValue().data; + } + + return (XN_STATUS_OK); +} + +XnStatus XnShiftToDepthStreamHelper::Free() +{ + XnShiftToDepthFree(&m_ShiftToDepthTables); + return XN_STATUS_OK; +} + +XnStatus XnShiftToDepthStreamHelper::InitShiftToDepth() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // register to any shift-to-depth property (so tables can be updated if needed) + XnUInt32 propIds[] = + { + XN_STREAM_PROPERTY_MIN_DEPTH, + XN_STREAM_PROPERTY_MAX_DEPTH, + XN_STREAM_PROPERTY_CONST_SHIFT, + XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR, + XN_STREAM_PROPERTY_PARAM_COEFF, + XN_STREAM_PROPERTY_SHIFT_SCALE, + XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, + XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, + XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, + XN_STREAM_PROPERTY_OUTPUT_FORMAT, + }; + + XnUInt32 nPropCount = sizeof(propIds) / sizeof(propIds[0]); + + XnCallbackHandle hDummy; + + XnProperty* pProperty = NULL; + for (XnUInt32 i = 0; i < nPropCount; ++i) + { + nRetVal = m_pModule->GetProperty(propIds[i], &pProperty); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProperty->OnChangeEvent().Register(ShiftToDepthPropertyValueChangedCallback, this, hDummy); + XN_IS_STATUS_OK(nRetVal); + } + + // register for tables size properties + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_MAX_SHIFT, &pProperty); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProperty->OnChangeEvent().Register(DeviceS2DTablesSizeChangedCallback, this, hDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_DEVICE_MAX_DEPTH, &pProperty); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProperty->OnChangeEvent().Register(DeviceS2DTablesSizeChangedCallback, this, hDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_OUTPUT_FORMAT, &pProperty); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProperty->OnChangeEvent().Register(DeviceS2DTablesSizeChangedCallback, this, hDummy); + XN_IS_STATUS_OK(nRetVal); + + // now init the tables + XnShiftToDepthConfig Config; + nRetVal = GetShiftToDepthConfig(Config); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnShiftToDepthInit(&m_ShiftToDepthTables, &Config); + XN_IS_STATUS_OK(nRetVal); + + // replace tables buffers + m_ShiftToDepthTable.ReplaceBuffer(m_ShiftToDepthTables.pShiftToDepthTable, m_ShiftToDepthTables.nShiftsCount * sizeof(OniDepthPixel)); + m_DepthToShiftTable.ReplaceBuffer(m_ShiftToDepthTables.pDepthToShiftTable, m_ShiftToDepthTables.nDepthsCount * sizeof(XnUInt16)); + + return (XN_STATUS_OK); +} + +XnStatus XnShiftToDepthStreamHelper::GetShiftToDepthConfig(XnShiftToDepthConfig& Config) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt64 nTemp; + XnDouble dTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nZeroPlaneDistance = (XnUInt16)nTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, &dTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.fZeroPlanePixelSize = (XnFloat)dTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, &dTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.fEmitterDCmosDistance = (XnFloat)dTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_MAX_SHIFT, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nDeviceMaxShiftValue = (XnUInt32)nTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_DEVICE_MAX_DEPTH, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nDeviceMaxDepthValue = (XnUInt32)nTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_CONST_SHIFT, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nConstShift = (XnUInt32)nTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nPixelSizeFactor = (XnUInt32)nTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_PARAM_COEFF, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nParamCoeff = (XnUInt32)nTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_SHIFT_SCALE, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nShiftScale = (XnUInt32)nTemp; + + // change scale according to output format + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_OUTPUT_FORMAT, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + switch (nTemp) + { + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + case ONI_PIXEL_FORMAT_SHIFT_9_2: + break; + case ONI_PIXEL_FORMAT_DEPTH_100_UM: + Config.nShiftScale *= 10; + break; + default: + XN_ASSERT(FALSE); + } + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_MIN_DEPTH, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nDepthMinCutOff = (OniDepthPixel)nTemp; + + nRetVal = m_pModule->GetProperty(XN_STREAM_PROPERTY_MAX_DEPTH, &nTemp); + XN_IS_STATUS_OK(nRetVal); + + Config.nDepthMaxCutOff = (OniDepthPixel)nTemp; + + return (XN_STATUS_OK); +} + +XnStatus XnShiftToDepthStreamHelper::RaiseChangeEvents() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_ShiftToDepthTable.UnsafeUpdateValue(XnGeneralBufferPack(m_ShiftToDepthTables.pShiftToDepthTable, m_ShiftToDepthTables.nShiftsCount * sizeof(OniDepthPixel))); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_DepthToShiftTable.UnsafeUpdateValue(XnGeneralBufferPack(m_ShiftToDepthTables.pDepthToShiftTable, m_ShiftToDepthTables.nDepthsCount * sizeof(XnUInt16))); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnShiftToDepthStreamHelper::OnShiftToDepthPropertyValueChanged() +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnShiftToDepthConfig Config; + nRetVal = GetShiftToDepthConfig(Config); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnShiftToDepthUpdate(&m_ShiftToDepthTables, &Config); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = RaiseChangeEvents(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnShiftToDepthStreamHelper::OnDeviceS2DTablesSizeChanged() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // free the tables, and re-init them + XnShiftToDepthFree(&m_ShiftToDepthTables); + + XnShiftToDepthConfig Config; + nRetVal = GetShiftToDepthConfig(Config); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnShiftToDepthInit(&m_ShiftToDepthTables, &Config); + XN_IS_STATUS_OK(nRetVal); + + // replace tables buffers + m_ShiftToDepthTable.ReplaceBuffer(m_ShiftToDepthTables.pShiftToDepthTable, m_ShiftToDepthTables.nShiftsCount * sizeof(OniDepthPixel)); + m_DepthToShiftTable.ReplaceBuffer(m_ShiftToDepthTables.pDepthToShiftTable, m_ShiftToDepthTables.nDepthsCount * sizeof(XnUInt16)); + + nRetVal = RaiseChangeEvents(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnShiftToDepthStreamHelper::GetShiftToDepthTableImpl(const OniGeneralBuffer& gbValue) const +{ + XnInt32 nTableSize = m_ShiftToDepthTables.nShiftsCount * sizeof(OniDepthPixel); + if (gbValue.dataSize < nTableSize) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(gbValue.data, m_ShiftToDepthTables.pShiftToDepthTable, nTableSize); + return XN_STATUS_OK; +} + +XnStatus XnShiftToDepthStreamHelper::GetDepthToShiftTableImpl(const OniGeneralBuffer& gbValue) const +{ + XnInt32 nTableSize = m_ShiftToDepthTables.nDepthsCount * sizeof(XnUInt16); + if (gbValue.dataSize < nTableSize) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(gbValue.data, m_ShiftToDepthTables.pDepthToShiftTable, nTableSize); + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnShiftToDepthStreamHelper::GetShiftToDepthTableCallback(const XnActualGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XnShiftToDepthStreamHelper* pStream = (XnShiftToDepthStreamHelper*)pCookie; + return pStream->GetShiftToDepthTableImpl(gbValue); +} + +XnStatus XN_CALLBACK_TYPE XnShiftToDepthStreamHelper::GetDepthToShiftTableCallback(const XnActualGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XnShiftToDepthStreamHelper* pStream = (XnShiftToDepthStreamHelper*)pCookie; + return pStream->GetDepthToShiftTableImpl(gbValue); +} + +XnStatus XN_CALLBACK_TYPE XnShiftToDepthStreamHelper::ShiftToDepthPropertyValueChangedCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnShiftToDepthStreamHelper* pStream = (XnShiftToDepthStreamHelper*)pCookie; + return pStream->OnShiftToDepthPropertyValueChanged(); +} + +XnStatus XN_CALLBACK_TYPE XnShiftToDepthStreamHelper::DeviceS2DTablesSizeChangedCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnShiftToDepthStreamHelper* pStream = (XnShiftToDepthStreamHelper*)pCookie; + return pStream->OnDeviceS2DTablesSizeChanged(); +} + diff --git a/Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.h b/Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.h new file mode 100644 index 0000000..fce20af --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnShiftToDepthStreamHelper.h @@ -0,0 +1,65 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SHIFT_TO_DEPTH_STREAM_HELPER_H__ +#define __XN_SHIFT_TO_DEPTH_STREAM_HELPER_H__ + +#include +#include + +class XnShiftToDepthStreamHelper +{ +public: + XnShiftToDepthStreamHelper(); + virtual ~XnShiftToDepthStreamHelper(); + + XnStatus Init(XnDeviceModule* pModule); + XnStatus Free(); + + inline OniDepthPixel* GetShiftToDepthTable() const { return m_ShiftToDepthTables.pShiftToDepthTable; } + inline XnUInt16* GetDepthToShiftTable() const { return m_ShiftToDepthTables.pDepthToShiftTable; } + +protected: + inline XnActualGeneralProperty& ShiftToDepthTableProperty() { return m_ShiftToDepthTable; } + inline XnActualGeneralProperty& DepthToShiftTableProperty() { return m_DepthToShiftTable; } + +private: + XnStatus RaiseChangeEvents(); + XnStatus InitShiftToDepth(); + XnStatus OnShiftToDepthPropertyValueChanged(); + XnStatus OnDeviceS2DTablesSizeChanged(); + XnStatus GetShiftToDepthConfig(XnShiftToDepthConfig& Config); + XnStatus GetShiftToDepthTableImpl(const OniGeneralBuffer& gbValue) const; + XnStatus GetDepthToShiftTableImpl(const OniGeneralBuffer& gbValue) const; + + // callbacks + static XnStatus XN_CALLBACK_TYPE GetShiftToDepthTableCallback(const XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetDepthToShiftTableCallback(const XnActualGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ShiftToDepthPropertyValueChangedCallback(const XnProperty* pSender, void* pCookie); + static XnStatus XN_CALLBACK_TYPE DeviceS2DTablesSizeChangedCallback(const XnProperty* pSender, void* pCookie); + + XnActualGeneralProperty m_ShiftToDepthTable; + XnActualGeneralProperty m_DepthToShiftTable; + XnShiftToDepthTables m_ShiftToDepthTables; + XnDeviceModule* m_pModule; + XnBool m_bPropertiesAdded; +}; + +#endif //__XN_SHIFT_TO_DEPTH_STREAM_HELPER_H__ diff --git a/Source/Drivers/PS1080/DDK/XnStreamingStream.cpp b/Source/Drivers/PS1080/DDK/XnStreamingStream.cpp new file mode 100644 index 0000000..3164851 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnStreamingStream.cpp @@ -0,0 +1,66 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnStreamingStream.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStreamingStream::XnStreamingStream(const XnChar* csType, const XnChar* csName) : + XnDeviceStream(csType, csName), + m_IsStreamingStream(XN_STREAM_PROPERTY_IS_STREAMING, "IsStreaming", TRUE), + m_ReadChunkSize(XN_STREAM_PROPERTY_READ_CHUNK_SIZE, "ReadChunkSize") +{ +} + +XnStatus XnStreamingStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnDeviceStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + m_ReadChunkSize.UpdateSetCallback(SetReadChunkSizeCallback, this); + + XN_VALIDATE_ADD_PROPERTIES(this, &m_IsStreamingStream, &m_ReadChunkSize); + + return (XN_STATUS_OK); +} + +XnStatus XnStreamingStream::SetReadChunkSize(XnUInt32 nChunkSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_ReadChunkSize.UnsafeUpdateValue(nChunkSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XN_CALLBACK_TYPE XnStreamingStream::SetReadChunkSizeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnStreamingStream* pStream = (XnStreamingStream*)pCookie; + return pStream->SetReadChunkSize((XnUInt32)nValue); +} diff --git a/Source/Drivers/PS1080/DDK/XnStreamingStream.h b/Source/Drivers/PS1080/DDK/XnStreamingStream.h new file mode 100644 index 0000000..789a0a6 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnStreamingStream.h @@ -0,0 +1,71 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_STREAMING_STREAM_H__ +#define __XN_STREAMING_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** Represents a base class for streams which are not frame based, by streaming. */ +class XnStreamingStream : public XnDeviceStream +{ +public: + XnStreamingStream(const XnChar* csType, const XnChar* csName); + ~XnStreamingStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Properties Getters + //--------------------------------------------------------------------------- + inline XnActualIntProperty& ReadChunkSizeProperty() { return m_ReadChunkSize; } + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + inline XnUInt32 GetReadChunkSize() { return (XnUInt32)m_ReadChunkSize.GetValue(); } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + virtual XnStatus SetReadChunkSize(XnUInt32 nChunkSize); + +private: + static XnStatus XN_CALLBACK_TYPE SetReadChunkSizeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnActualIntProperty m_IsStreamingStream; + XnActualIntProperty m_ReadChunkSize; +}; + +#endif //__XN_STREAMING_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DDK/XnStringProperty.cpp b/Source/Drivers/PS1080/DDK/XnStringProperty.cpp new file mode 100644 index 0000000..d5daa14 --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnStringProperty.cpp @@ -0,0 +1,92 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnStringProperty.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStringProperty::XnStringProperty(XnUInt32 propertyId, const XnChar* strName, XnChar* pValueHolder, const XnChar* strModule /* = "" */) : + XnProperty(XN_PROPERTY_TYPE_STRING, pValueHolder, propertyId, strName, strModule) +{ +} + +XnStatus XnStringProperty::ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnChar csValue[XN_DEVICE_MAX_STRING_LENGTH]; + nRetVal = xnOSReadStringFromINI(csINIFile, csSection, GetName(), csValue, XN_DEVICE_MAX_STRING_LENGTH); + if (nRetVal == XN_STATUS_OK) + { + nRetVal = SetValue(csValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnStringProperty::CopyValueImpl(void* pDest, const void* pSource) const +{ + strncpy((char*)pDest, (const char*)pSource, XN_DEVICE_MAX_STRING_LENGTH); + return XN_STATUS_OK; +} + +XnBool XnStringProperty::IsEqual(const void* pValue1, const void* pValue2) const +{ + return (strncmp((const XnChar*)pValue1, (const XnChar*)pValue2, XN_DEVICE_MAX_STRING_LENGTH) == 0); +} + +XnStatus XnStringProperty::CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie) +{ + SetFuncPtr pCallback = (SetFuncPtr)pFunc; + return pCallback(this, (const XnChar*)pValue, pCookie); +} + +XnStatus XnStringProperty::CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const +{ + GetFuncPtr pCallback = (GetFuncPtr)pFunc; + return pCallback(this, (XnChar*)pValue, pCookie); +} + +XnBool XnStringProperty::ConvertValueToString(XnChar* csValue, const void* pValue) const +{ + strncpy(csValue, (const XnChar*)pValue, XN_DEVICE_MAX_STRING_LENGTH); + return TRUE; +} + +XnStatus XnStringProperty::AddToPropertySet(XnPropertySet* pSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnChar strValue[XN_DEVICE_MAX_STRING_LENGTH]; + nRetVal = GetValue(strValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnPropertySetAddStringProperty(pSet, GetModule(), GetId(), strValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + diff --git a/Source/Drivers/PS1080/DDK/XnStringProperty.h b/Source/Drivers/PS1080/DDK/XnStringProperty.h new file mode 100644 index 0000000..50678df --- /dev/null +++ b/Source/Drivers/PS1080/DDK/XnStringProperty.h @@ -0,0 +1,87 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_STRING_PROPERTY_H__ +#define __XN_STRING_PROPERTY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Class +//--------------------------------------------------------------------------- + +/** +* A property of type general. +*/ +class XnStringProperty : public XnProperty +{ +public: + XnStringProperty(XnUInt32 propertyId, const XnChar* strName, XnChar* pValueHolder, const XnChar* strModule = ""); + + typedef XnStatus (XN_CALLBACK_TYPE* SetFuncPtr)(XnStringProperty* pSender, const XnChar* strValue, void* pCookie); + typedef XnStatus (XN_CALLBACK_TYPE* GetFuncPtr)(const XnStringProperty* pSender, XnChar* csValue, void* pCookie); + + inline XnStatus SetValue(const XnChar* strValue) + { + XN_VALIDATE_INPUT_PTR(strValue); + return XnProperty::SetValue(strValue); + } + + inline XnStatus GetValue(XnChar* csValue) const + { + XN_VALIDATE_INPUT_PTR(csValue); + return XnProperty::GetValue(csValue); + } + + inline XnStatus UnsafeUpdateValue(const XnChar* strValue) + { + XN_VALIDATE_INPUT_PTR(strValue); + return XnProperty::UnsafeUpdateValue(strValue); + } + + inline void UpdateSetCallback(SetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateSetCallback((XnProperty::SetFuncPtr)pFunc, pCookie); + } + + inline void UpdateGetCallback(GetFuncPtr pFunc, void* pCookie) + { + XnProperty::UpdateGetCallback((XnProperty::GetFuncPtr)pFunc, pCookie); + } + + XnStatus ReadValueFromFile(const XnChar* csINIFile, const XnChar* csSection); + + XnStatus AddToPropertySet(XnPropertySet* pSet); + +protected: + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + virtual XnStatus CopyValueImpl(void* pDest, const void* pSource) const; + virtual XnBool IsEqual(const void* pValue1, const void* pValue2) const; + virtual XnStatus CallSetCallback(XnProperty::SetFuncPtr pFunc, const void* pValue, void* pCookie); + virtual XnStatus CallGetCallback(XnProperty::GetFuncPtr pFunc, void* pValue, void* pCookie) const; + virtual XnBool ConvertValueToString(XnChar* csValue, const void* pValue) const; +}; + +#endif //__XN_STRING_PROPERTY_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DriverImpl/XnExportedOniDriver.cpp b/Source/Drivers/PS1080/DriverImpl/XnExportedOniDriver.cpp new file mode 100644 index 0000000..16b0b56 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnExportedOniDriver.cpp @@ -0,0 +1,32 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniDriver.h" + +//--------------------------------------------------------------------------- +// XnOniDriver EXPORT +//--------------------------------------------------------------------------- +ONI_EXPORT_DRIVER(XnOniDriver); + +// The following line is needed to be once in *ALL* of the high level shared library modules. DO NOT REMOVE!!! +XN_API_EXPORT_INIT() diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniColorStream.cpp b/Source/Drivers/PS1080/DriverImpl/XnOniColorStream.cpp new file mode 100644 index 0000000..4bf5fed --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniColorStream.cpp @@ -0,0 +1,147 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniColorStream.h" +#include "../Sensor/XnSensorImageStream.h" + +//--------------------------------------------------------------------------- +// Static Data +//--------------------------------------------------------------------------- +static const XnUInt32 INVALID_INPUT_FORMAT = 9999; +// the order in the allowed input formats is the preferred one +static XnIOImageFormats g_anAllowedRGBFormats[] = { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_IO_IMAGE_FORMAT_YUV422, XN_IO_IMAGE_FORMAT_BAYER, XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER, XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV }; +static XnIOImageFormats g_anAllowedYUVFormats[] = { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_IO_IMAGE_FORMAT_YUV422 }; +static XnIOImageFormats g_anAllowedYUYVFormats[] = { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV }; +static XnIOImageFormats g_anAllowedJPEGFormats[] = { XN_IO_IMAGE_FORMAT_JPEG }; +static XnIOImageFormats g_anAllowedGray8Formats[] = { XN_IO_IMAGE_FORMAT_BAYER, XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER }; + +void XnOniColorStream::GetAllowedOniOutputFormatForInputFormat(XnIOImageFormats inputFormat, OniPixelFormat *aOniFormats, int *nOniFormats) +{ + *nOniFormats = 0; + for(XnUInt32 i=0; i<(sizeof(g_anAllowedRGBFormats)/sizeof(XnIOImageFormats)); ++i) + { + if(g_anAllowedRGBFormats[i] == inputFormat) + { + aOniFormats[*nOniFormats] = ONI_PIXEL_FORMAT_RGB888; + ++(*nOniFormats); + break; + } + } + for(XnUInt32 i=0; i<(sizeof(g_anAllowedYUVFormats)/sizeof(XnIOImageFormats)); ++i) + { + if(g_anAllowedYUVFormats[i] == inputFormat) + { + aOniFormats[*nOniFormats] = ONI_PIXEL_FORMAT_YUV422; + ++(*nOniFormats); + break; + } + } + for(XnUInt32 i=0; i<(sizeof(g_anAllowedYUYVFormats)/sizeof(XnIOImageFormats)); ++i) + { + if(g_anAllowedYUYVFormats[i] == inputFormat) + { + aOniFormats[*nOniFormats] = ONI_PIXEL_FORMAT_YUYV; + ++(*nOniFormats); + break; + } + } + for(XnUInt32 i=0; i<(sizeof(g_anAllowedJPEGFormats)/sizeof(XnIOImageFormats)); ++i) + { + if(g_anAllowedJPEGFormats[i] == inputFormat) + { + aOniFormats[*nOniFormats] = ONI_PIXEL_FORMAT_JPEG; + ++(*nOniFormats); + break; + } + } + for(XnUInt32 i=0; i<(sizeof(g_anAllowedGray8Formats)/sizeof(XnIOImageFormats)); ++i) + { + if(g_anAllowedGray8Formats[i] == inputFormat) + { + aOniFormats[*nOniFormats] = ONI_PIXEL_FORMAT_GRAY8; + ++(*nOniFormats); + break; + } + } +} + +XnBool XnOniColorStream::IsSupportedInputFormat(XnIOImageFormats inputFormat, OniPixelFormat oniFormat) +{ + return IsPreferredInputFormat(inputFormat, (XnIOImageFormats)XN_MAX_UINT32, oniFormat); +} + +XnBool XnOniColorStream::IsPreferredInputFormat(XnIOImageFormats inputFormat, XnIOImageFormats thanFormat, OniPixelFormat oniFormat) +{ + XnIOImageFormats *aAllowedFormats; + int nAllowedFormats; + + switch(oniFormat) + { + case ONI_PIXEL_FORMAT_RGB888: + aAllowedFormats = g_anAllowedRGBFormats; + nAllowedFormats = sizeof(g_anAllowedRGBFormats)/sizeof(g_anAllowedRGBFormats[0]); + break; + case ONI_PIXEL_FORMAT_YUV422: + aAllowedFormats = g_anAllowedYUVFormats; + nAllowedFormats = sizeof(g_anAllowedYUVFormats)/sizeof(g_anAllowedYUVFormats[0]); + break; + case ONI_PIXEL_FORMAT_YUYV: + aAllowedFormats = g_anAllowedYUYVFormats; + nAllowedFormats = sizeof(g_anAllowedYUYVFormats)/sizeof(g_anAllowedYUYVFormats[0]); + break; + case ONI_PIXEL_FORMAT_JPEG: + aAllowedFormats = g_anAllowedJPEGFormats; + nAllowedFormats = sizeof(g_anAllowedJPEGFormats)/sizeof(g_anAllowedJPEGFormats[0]); + break; + case ONI_PIXEL_FORMAT_GRAY8: + aAllowedFormats = g_anAllowedGray8Formats; + nAllowedFormats = sizeof(g_anAllowedGray8Formats)/sizeof(g_anAllowedGray8Formats[0]); + break; + default: + return FALSE; + } + + for(int i=0; iGetProperty(m_strType, XN_STREAM_PROPERTY_DEVICE_MAX_DEPTH, &nValue); + + *(int*)data = (int)nValue; + return ONI_STATUS_OK; + case ONI_STREAM_PROPERTY_MIN_VALUE: + if (*pDataSize != sizeof(int)) + { + return ONI_STATUS_BAD_PARAMETER; + } + + *(int*)data = 0; + return ONI_STATUS_OK; + case XN_STREAM_PROPERTY_DEPTH_SENSOR_CALIBRATION_INFO: + return ((XnSensorDepthStream*)m_pDeviceStream)->GetSensorCalibrationInfo(data, pDataSize); + default: + return XnOniMapStream::getProperty(propertyId, data, pDataSize); + } +} + +OniBool XnOniDepthStream::isPropertySupported(int propertyId) +{ + return ( + propertyId == ONI_STREAM_PROPERTY_MAX_VALUE || + propertyId == ONI_STREAM_PROPERTY_MIN_VALUE || + propertyId == XN_STREAM_PROPERTY_DEPTH_SENSOR_CALIBRATION_INFO || + XnOniMapStream::isPropertySupported(propertyId)); +} + +void XnOniDepthStream::notifyAllProperties() +{ + XnOniMapStream::notifyAllProperties(); + + XnUInt32 nValue; + int size = sizeof(nValue); + + // white balance + getProperty(XN_STREAM_PROPERTY_WHITE_BALANCE_ENABLED, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_WHITE_BALANCE_ENABLED, &nValue, size); + + // gain + getProperty(XN_STREAM_PROPERTY_GAIN, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_GAIN, &nValue, size); + + // hole filter + getProperty(XN_STREAM_PROPERTY_HOLE_FILTER, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_HOLE_FILTER, &nValue, size); + + // registration type + getProperty(XN_STREAM_PROPERTY_REGISTRATION_TYPE, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_REGISTRATION_TYPE, &nValue, size); + + // const shift + getProperty(XN_STREAM_PROPERTY_CONST_SHIFT, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_CONST_SHIFT, &nValue, size); + + // pixel size factor + getProperty(XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_PIXEL_SIZE_FACTOR, &nValue, size); + + // max shift + getProperty(XN_STREAM_PROPERTY_MAX_SHIFT, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_MAX_SHIFT, &nValue, size); + + // param coeff + getProperty(XN_STREAM_PROPERTY_PARAM_COEFF, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_PARAM_COEFF, &nValue, size); + + // shift scale + getProperty(XN_STREAM_PROPERTY_SHIFT_SCALE, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_SHIFT_SCALE, &nValue, size); + + // zero plane distance + getProperty(XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, &nValue, size); + + XnDouble dValue; + size = sizeof(dValue); + + // zero plane pixel size + getProperty(XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, &dValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, &dValue, size); + + // emitter/depth-cmos distance + getProperty(XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, &dValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE, &dValue, size); + + // depth-cmos/RGB-cmos distance + getProperty(XN_STREAM_PROPERTY_DCMOS_RCMOS_DISTANCE, &dValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_DCMOS_RCMOS_DISTANCE, &dValue, size); + + XnSensorDepthStream* pDepthStream = (XnSensorDepthStream*)m_pDeviceStream; + + // shift-to-depth table + raisePropertyChanged(XN_STREAM_PROPERTY_S2D_TABLE, pDepthStream->GetShiftToDepthTable(), sizeof(OniDepthPixel)*(pDepthStream->GetMaxShift()+1)); + + // depth-to-shift table + raisePropertyChanged(XN_STREAM_PROPERTY_D2S_TABLE, pDepthStream->GetDepthToShiftTable(), sizeof(XnUInt16)*(pDepthStream->GetDeviceMaxDepth()+1)); + + DepthUtilsSensorCalibrationInfo calibrationInfo; + size = sizeof(DepthUtilsSensorCalibrationInfo); + ((XnSensorDepthStream*)m_pDeviceStream)->GetSensorCalibrationInfo(&calibrationInfo, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_DEPTH_SENSOR_CALIBRATION_INFO, &calibrationInfo, size); +} + +OniStatus XnOniDepthStream::convertDepthToColorCoordinates(StreamBase* colorStream, int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY) +{ + // take video mode from the color stream + XnOniMapStream* pColorStream = (XnOniMapStream*)colorStream; + + OniVideoMode videoMode; + XnStatus retVal = pColorStream->GetVideoMode(&videoMode); + if (retVal != XN_STATUS_OK) + { + XN_ASSERT(FALSE); + return ONI_STATUS_ERROR; + } + + // translate it to the internal property + XnPixelRegistration pixelArgs; + pixelArgs.nDepthX = depthX; + pixelArgs.nDepthY = depthY; + pixelArgs.nDepthValue = depthZ; + pixelArgs.nImageXRes = videoMode.resolutionX; + pixelArgs.nImageYRes = videoMode.resolutionY; + int pixelArgsSize = sizeof(pixelArgs); + + if (ONI_STATUS_OK != getProperty(XN_STREAM_PROPERTY_PIXEL_REGISTRATION, &pixelArgs, &pixelArgsSize)) + { + return ONI_STATUS_ERROR; + } + + // take output + *pColorX = pixelArgs.nImageX; + *pColorY = pixelArgs.nImageY; + + return ONI_STATUS_OK; +} + diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniDepthStream.h b/Source/Drivers/PS1080/DriverImpl/XnOniDepthStream.h new file mode 100644 index 0000000..0856961 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniDepthStream.h @@ -0,0 +1,44 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ONI_DEPTH_STREAM_H__ +#define __XN_ONI_DEPTH_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniMapStream.h" +#include "../Sensor/XnSensor.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnOniDepthStream : + public XnOniMapStream +{ +public: + XnOniDepthStream(XnSensor* pSensor, XnOniDevice* pDevice); + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniBool isPropertySupported(int propertyId); + virtual void notifyAllProperties(); + virtual OniStatus convertDepthToColorCoordinates(StreamBase* colorStream, int depthX, int depthY, OniDepthPixel depthZ, int* pColorX, int* pColorY); +}; + +#endif // __XN_ONI_DEPTH_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniDevice.cpp b/Source/Drivers/PS1080/DriverImpl/XnOniDevice.cpp new file mode 100644 index 0000000..6ea6e69 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniDevice.cpp @@ -0,0 +1,532 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniDevice.h" +#include "XnOniStream.h" +#include "XnOniDriver.h" +#include "../Sensor/XnDeviceEnumeration.h" +#include "../DDK/XnPropertySetInternal.h" + +//--------------------------------------------------------------------------- +// XnOniDevice class +//--------------------------------------------------------------------------- +XnOniDevice::XnOniDevice(const XnChar* uri, oni::driver::DriverServices& driverServices, XnOniDriver* pDriver) : m_driverServices(driverServices), m_pDriver(pDriver) +{ + xnOSMemCopy(&m_info, XnDeviceEnumeration::GetDeviceInfo(uri), sizeof(m_info)); +} + +XnOniDevice::~XnOniDevice() +{ + // free the allocated arrays + for(int i=0; i < m_numSensors; ++i) + { + XN_DELETE_ARR(m_sensors[i].pSupportedVideoModes); + } + m_sensor.Destroy(); +} + +XnStatus XnOniDevice::FillSupportedVideoModes() +{ + XnUInt32 nSupportedModes = 0; + XnCmosPreset* pSupportedModes = NULL; + + int s = 0; + + // Depth + nSupportedModes = m_sensor.GetDevicePrivateData()->FWInfo.depthModes.GetSize(); + pSupportedModes = m_sensor.GetDevicePrivateData()->FWInfo.depthModes.GetData(); + + m_sensors[s].sensorType = ONI_SENSOR_DEPTH; + m_sensors[s].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, nSupportedModes); + XN_VALIDATE_ALLOC_PTR(m_sensors[s].pSupportedVideoModes); + + OniPixelFormat depthFormats[] = { ONI_PIXEL_FORMAT_DEPTH_1_MM, ONI_PIXEL_FORMAT_DEPTH_100_UM }; + XnSizeT depthFormatsCount = sizeof(depthFormats) / sizeof(depthFormats[0]); + + int writeIndex = 0; + for(XnUInt32 i = 0; i < nSupportedModes; ++i) + { + for (XnSizeT formatIndex = 0; formatIndex < depthFormatsCount; ++formatIndex) + { + m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat = depthFormats[formatIndex]; + m_sensors[s].pSupportedVideoModes[writeIndex].fps = pSupportedModes[i].nFPS; + XnBool bOK = XnDDKGetXYFromResolution( + (XnResolutions)pSupportedModes[i].nResolution, + (XnUInt32*)&m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX, + (XnUInt32*)&m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY + ); + XN_ASSERT(bOK); + XN_REFERENCE_VARIABLE(bOK); + + bool foundMatch = false; + for (int j = 0; j < writeIndex; ++j) + { + if (m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat == m_sensors[s].pSupportedVideoModes[j].pixelFormat && + m_sensors[s].pSupportedVideoModes[writeIndex].fps == m_sensors[s].pSupportedVideoModes[j].fps && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX == m_sensors[s].pSupportedVideoModes[j].resolutionX && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY == m_sensors[s].pSupportedVideoModes[j].resolutionY) + { + // Already know this configuration + foundMatch = true; + break; + } + } + if (!foundMatch) + { + ++writeIndex; + } + } + } + m_sensors[s].numSupportedVideoModes = writeIndex; + + // Image + + // first, make sure that our sensor actually supports Image + XnUInt64 nImageSupported = FALSE; + XnStatus nRetVal = m_sensor.GetProperty(XN_MASK_DEVICE, XN_MODULE_PROPERTY_IMAGE_SUPPORTED, &nImageSupported); + XN_IS_STATUS_OK(nRetVal); + if (nImageSupported) + { + ++s; + nSupportedModes = m_sensor.GetDevicePrivateData()->FWInfo.imageModes.GetSize(); + pSupportedModes = m_sensor.GetDevicePrivateData()->FWInfo.imageModes.GetData(); + + m_sensors[s].sensorType = ONI_SENSOR_COLOR; + m_sensors[s].numSupportedVideoModes = 0; // to be changed later.. + m_sensors[s].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, nSupportedModes * 10); + XN_VALIDATE_ALLOC_PTR(m_sensors[s].pSupportedVideoModes); + + writeIndex = 0; + for(XnUInt32 j=0; j < nSupportedModes; ++j) + { + // make an OniVideoMode for each OniFormat supported by the input format + OniPixelFormat aOniFormats[10]; + int nOniFormats = 0; + XnOniColorStream::GetAllowedOniOutputFormatForInputFormat((XnIOImageFormats)pSupportedModes[j].nFormat, aOniFormats, &nOniFormats); + for(int curOni=0; curOniFWInfo.irModes.GetSize(); + pSupportedModes = m_sensor.GetDevicePrivateData()->FWInfo.irModes.GetData(); + + m_sensors[s].sensorType = ONI_SENSOR_IR; + m_sensors[s].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, nSupportedModes*2); + XN_VALIDATE_ALLOC_PTR(m_sensors[s].pSupportedVideoModes); + + OniPixelFormat irFormats[] = {ONI_PIXEL_FORMAT_GRAY16, ONI_PIXEL_FORMAT_RGB888}; + writeIndex = 0; + for(XnUInt32 i=0; i < nSupportedModes; ++i) + { + for (int fmt = 0; fmt <= 1; ++fmt) + { + m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat = irFormats[fmt]; + m_sensors[s].pSupportedVideoModes[writeIndex].fps = pSupportedModes[i].nFPS; + XnBool bOK = XnDDKGetXYFromResolution( + (XnResolutions)pSupportedModes[i].nResolution, + (XnUInt32*)&m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX, + (XnUInt32*)&m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY + ); + XN_ASSERT(bOK); + XN_REFERENCE_VARIABLE(bOK); + + bool foundMatch = false; + for (int j = 0; j < writeIndex; ++j) + { + if (m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat == m_sensors[s].pSupportedVideoModes[j].pixelFormat && + m_sensors[s].pSupportedVideoModes[writeIndex].fps == m_sensors[s].pSupportedVideoModes[j].fps && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX == m_sensors[s].pSupportedVideoModes[j].resolutionX && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY == m_sensors[s].pSupportedVideoModes[j].resolutionY) + { + // Already know this configuration + foundMatch = true; + break; + } + } + if (!foundMatch) + { + ++writeIndex; + } + } + } + m_sensors[s].numSupportedVideoModes = writeIndex; + m_numSensors = s+1; + + return XN_STATUS_OK; +} + +XnStatus XnOniDevice::Init(const char* mode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_PROPERTY_SET_CREATE_ON_STACK(initialValues); + + if (mode != NULL) + { + nRetVal = XnPropertySetAddModule(&initialValues, XN_MODULE_NAME_DEVICE); + XN_IS_STATUS_OK(nRetVal); + + for (int i = 0; mode[i] != '\0'; ++i) + { + switch (mode[i]) + { + case 'L': + nRetVal = XnPropertySetAddIntProperty(&initialValues, XN_MODULE_NAME_DEVICE, XN_MODULE_PROPERTY_LEAN_INIT, TRUE); + XN_IS_STATUS_OK(nRetVal); + break; + case 'R': + nRetVal = XnPropertySetAddIntProperty(&initialValues, XN_MODULE_NAME_DEVICE, XN_MODULE_PROPERTY_RESET_SENSOR_ON_STARTUP, FALSE); + XN_IS_STATUS_OK(nRetVal); + break; + } + } + } + + XnDeviceConfig config; + config.cpConnectionString = m_info.uri; + config.pInitialValues = &initialValues; + XnStatus retVal = m_sensor.Init(&config); + XN_IS_STATUS_OK(retVal); + + nRetVal = FillSupportedVideoModes(); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +OniStatus XnOniDevice::getSensorInfoList(OniSensorInfo** pSensors, int* numSensors) +{ + *numSensors = m_numSensors; + *pSensors = m_sensors; + + return ONI_STATUS_OK; +} + +oni::driver::StreamBase* XnOniDevice::createStream(OniSensorType sensorType) +{ + XnOniStream* pStream; + + if (sensorType == ONI_SENSOR_DEPTH) + { + pStream = XN_NEW(XnOniDepthStream, &m_sensor, this); + } + else if (sensorType == ONI_SENSOR_COLOR) + { + pStream = XN_NEW(XnOniColorStream, &m_sensor, this); + } + else if (sensorType == ONI_SENSOR_IR) + { + pStream = XN_NEW(XnOniIRStream, &m_sensor, this); + } + else + { + m_driverServices.errorLoggerAppend("XnOniDevice: Can't create a stream of type %d", sensorType); + return NULL; + } + + XnStatus nRetVal = pStream->Init(); + if (nRetVal != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("XnOniDevice: Can't initialize stream of type %d: %s", sensorType, xnGetStatusString(nRetVal)); + XN_DELETE(pStream); + return NULL; + } + + return pStream; +} + +void XnOniDevice::destroyStream(oni::driver::StreamBase* pStream) +{ + XN_DELETE(pStream); +} + +OniStatus XnOniDevice::getProperty(int propertyId, void* data, int* pDataSize) +{ + switch (propertyId) + { + case ONI_DEVICE_PROPERTY_FIRMWARE_VERSION: + { + XnVersions &versions = m_sensor.GetDevicePrivateData()->Version; + XnUInt32 nCharsWritten = 0; + XnStatus rc = xnOSStrFormat((XnChar*)data, *pDataSize, &nCharsWritten, "%d.%d.%d", versions.nMajor, versions.nMinor, versions.nBuild); + if (rc != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Couldn't get firmware version: %s\n", xnGetStatusString(rc)); + return ONI_STATUS_BAD_PARAMETER; + } + *pDataSize = nCharsWritten+1; + + break; + } + case ONI_DEVICE_PROPERTY_HARDWARE_VERSION: + { + XnVersions &versions = m_sensor.GetDevicePrivateData()->Version; + int hwVer = versions.HWVer; + if (*pDataSize == sizeof(int)) + { + (*((int*)data)) = hwVer; + } + else if (*pDataSize == sizeof(short)) + { + (*((short*)data)) = (short)hwVer; + } + else if (*pDataSize == sizeof(uint64_t)) + { + (*((uint64_t*)data)) = (uint64_t)hwVer; + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d or %d or %d\n", *pDataSize, sizeof(short), sizeof(int), sizeof(uint64_t)); + return ONI_STATUS_ERROR; + } + break; + } + case ONI_DEVICE_PROPERTY_SERIAL_NUMBER: + { + XnStatus rc = m_sensor.DeviceModule()->GetProperty(XN_MODULE_PROPERTY_SERIAL_NUMBER, data, pDataSize); + if (rc != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Couldn't get serial number: %s\n", xnGetStatusString(rc)); + return ONI_STATUS_BAD_PARAMETER; + } + + break; + } + case ONI_DEVICE_PROPERTY_DRIVER_VERSION: + { + if (*pDataSize == sizeof(OniVersion)) + { + OniVersion* version = (OniVersion*)data; + version->major = XN_PS_MAJOR_VERSION; + version->minor = XN_PS_MINOR_VERSION; + version->maintenance = XN_PS_MAINTENANCE_VERSION; + version->build = XN_PS_BUILD_VERSION; + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniVersion)); + return ONI_STATUS_ERROR; + } + } + break; + case ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION: + { + if (*pDataSize == sizeof(OniImageRegistrationMode)) + { + OniImageRegistrationMode* mode = (OniImageRegistrationMode*)data; + + // Find the depth stream in the sensor. + XnDeviceStream* pDepth = NULL; + XnStatus xnrc = m_sensor.GetStream(XN_STREAM_NAME_DEPTH, &pDepth); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Set the mode in the depth stream. + XnUInt64 val; + xnrc = pDepth->GetProperty(XN_STREAM_PROPERTY_REGISTRATION, &val); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + // Update the return value. + *mode = (val == 1) ? ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR : ONI_IMAGE_REGISTRATION_OFF; + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniImageRegistrationMode)); + return ONI_STATUS_ERROR; + } + } + break; + default: + XnStatus nRetVal = m_sensor.DeviceModule()->GetProperty(propertyId, data, pDataSize); + if (nRetVal != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Failed to set property %x: %s", propertyId, xnGetStatusString(nRetVal)); + return ONI_STATUS_BAD_PARAMETER; + } + } + + return ONI_STATUS_OK; +} + +OniStatus XnOniDevice::setProperty(int propertyId, const void* data, int dataSize) +{ + switch (propertyId) + { + case ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION: + { + if (dataSize == sizeof(OniImageRegistrationMode)) + { + OniImageRegistrationMode* mode = (OniImageRegistrationMode*)data; + + // Find the depth stream in the sensor. + XnDeviceStream* pDepth = NULL; + XnStatus xnrc = m_sensor.GetStream(XN_STREAM_NAME_DEPTH, &pDepth); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Set the mode in the depth stream. + XnUInt64 val = (*mode == ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR) ? 1 : 0; + xnrc = pDepth->SetProperty(XN_STREAM_PROPERTY_REGISTRATION, val); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(OniImageRegistrationMode)); + return ONI_STATUS_ERROR; + } + } + break; + default: + XnStatus nRetVal = m_sensor.DeviceModule()->SetProperty(propertyId, data, dataSize); + if (nRetVal != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Failed to set property %x: %s", propertyId, xnGetStatusString(nRetVal)); + return ONI_STATUS_BAD_PARAMETER; + } + } + return ONI_STATUS_OK; +} +OniBool XnOniDevice::isPropertySupported(int propertyId) +{ + if (propertyId == ONI_DEVICE_PROPERTY_DRIVER_VERSION || + propertyId == ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION || + propertyId == ONI_DEVICE_PROPERTY_FIRMWARE_VERSION || + propertyId == ONI_DEVICE_PROPERTY_HARDWARE_VERSION || + propertyId == ONI_DEVICE_PROPERTY_SERIAL_NUMBER) + { + return TRUE; + } + else + { + XnBool propertyExists = FALSE; + m_sensor.DeviceModule()->DoesPropertyExist(propertyId, &propertyExists); + return propertyExists; + } +} + +void XnOniDevice::notifyAllProperties() +{ + XnUInt32 nValue = (XnUInt32)m_sensor.GetCurrentUsbInterface(); + int size = sizeof(nValue); + raisePropertyChanged(XN_MODULE_PROPERTY_USB_INTERFACE, &nValue, sizeof(nValue)); + + nValue = m_sensor.GetDeviceMirror(); + raisePropertyChanged(XN_MODULE_PROPERTY_MIRROR, &nValue, sizeof(nValue)); + + nValue = m_sensor.GetDeviceMirror(); + raisePropertyChanged(XN_STREAM_PROPERTY_CLOSE_RANGE, &nValue, sizeof(nValue)); + + getProperty(XN_MODULE_PROPERTY_RESET_SENSOR_ON_STARTUP, &nValue, &size); + raisePropertyChanged(XN_MODULE_PROPERTY_RESET_SENSOR_ON_STARTUP, &nValue, sizeof(nValue)); + + getProperty(XN_MODULE_PROPERTY_LEAN_INIT, &nValue, &size); + raisePropertyChanged(XN_MODULE_PROPERTY_LEAN_INIT, &nValue, sizeof(nValue)); + + XnChar strValue[XN_DEVICE_MAX_STRING_LENGTH]; + size = sizeof(strValue); + getProperty(XN_MODULE_PROPERTY_SERIAL_NUMBER, strValue, &size); + raisePropertyChanged(XN_MODULE_PROPERTY_SERIAL_NUMBER, strValue, size); + + XnVersions versions; + size = sizeof(versions); + getProperty(XN_MODULE_PROPERTY_VERSION, &versions, &size); + raisePropertyChanged(XN_MODULE_PROPERTY_VERSION, &versions, size); +} + +OniStatus XnOniDevice::EnableFrameSync(XnOniStream** pStreams, int streamCount) +{ + // Translate the XnOniStream to XnDeviceStream. + xnl::Array streams(streamCount); + streams.SetSize(streamCount); + for (int i = 0; i < streamCount; ++i) + { + streams[i] = pStreams[i]->GetDeviceStream(); + } + + // Set the frame sync group. + XnStatus rc = m_sensor.SetFrameSyncStreamGroup(streams.GetData(), streamCount); + if (rc != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Error setting frame-sync group (rc=%d)\n", rc); + return ONI_STATUS_ERROR; + } + + return ONI_STATUS_OK; +} + +void XnOniDevice::DisableFrameSync() +{ + XnStatus rc = m_sensor.SetFrameSyncStreamGroup(NULL, 0); + if (rc != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Error setting frame-sync group (rc=%d)\n", rc); + } +} + +OniBool XnOniDevice::isImageRegistrationModeSupported(OniImageRegistrationMode mode) +{ + return (mode == ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR || mode == ONI_IMAGE_REGISTRATION_OFF); +} diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniDevice.h b/Source/Drivers/PS1080/DriverImpl/XnOniDevice.h new file mode 100644 index 0000000..d2ca94e --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniDevice.h @@ -0,0 +1,88 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ONI_DEVICE_H__ +#define __XN_ONI_DEVICE_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include "XnOniDepthStream.h" +#include "XnOniColorStream.h" +#include "XnOniIRStream.h" +#include "../Sensor/XnSensor.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +class XnOniStream; +class XnOniDriver; + +class XnOniDevice : + public oni::driver::DeviceBase +{ +public: + XnOniDevice(const char* uri, oni::driver::DriverServices& driverServices, XnOniDriver* pDriver); + virtual ~XnOniDevice(); + + XnStatus Init(const char* mode); + + OniDeviceInfo* GetInfo() { return &m_info; } + + OniStatus getSensorInfoList(OniSensorInfo** pSensors, int* numSensors); + + oni::driver::StreamBase* createStream(OniSensorType sensorType); + void destroyStream(oni::driver::StreamBase* pStream); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + virtual OniBool isPropertySupported(int propertyId); + virtual void notifyAllProperties(); + + virtual OniStatus EnableFrameSync(XnOniStream** pStreams, int streamCount); + virtual void DisableFrameSync(); + + virtual OniBool isImageRegistrationModeSupported(OniImageRegistrationMode mode); + + XnSensor* GetSensor() + { + return &m_sensor; + } + + XnOniDriver* GetDriver() + { + return m_pDriver; + } + +private: + XnStatus FillSupportedVideoModes(); + + OniDeviceInfo m_info; + int m_numSensors; + OniSensorInfo m_sensors[10]; + oni::driver::DriverServices& m_driverServices; + XnSensor m_sensor; + XnOniDriver* m_pDriver; +}; + +#endif // __XN_ONI_DEVICE_H__ diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniDriver.cpp b/Source/Drivers/PS1080/DriverImpl/XnOniDriver.cpp new file mode 100644 index 0000000..84c5a69 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniDriver.cpp @@ -0,0 +1,282 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniDriver.h" +#include "XnOniDevice.h" +#include +#include +#include "../Sensor/XnSensor.h" +#include "../Sensor/XnDeviceEnumeration.h" +#include + +//--------------------------------------------------------------------------- +// XnOniDriver class +//--------------------------------------------------------------------------- +XnOniDriver::XnOpenNILogWriter::XnOpenNILogWriter(OniDriverServices* pDriverServices) : m_pDriverServices(pDriverServices) +{ +} + +void XnOniDriver::XnOpenNILogWriter::WriteEntry(const XnLogEntry* pEntry) +{ + m_pDriverServices->log(m_pDriverServices, pEntry->nSeverity, pEntry->strFile, pEntry->nLine, pEntry->strMask, pEntry->strMessage); +} + +void XnOniDriver::XnOpenNILogWriter::WriteUnformatted(const XnChar* /*strMessage*/) +{ + // DO NOTHING +} + +OniStatus XnOniDriver::initialize(oni::driver::DeviceConnectedCallback deviceConnectedCallback, oni::driver::DeviceDisconnectedCallback deviceDisconnectedCallback, oni::driver::DeviceStateChangedCallback deviceStateChangedCallback, void* pCookie) +{ + OniStatus nRetVal = DriverBase::initialize(deviceConnectedCallback, deviceDisconnectedCallback, deviceStateChangedCallback, pCookie); + if (nRetVal != ONI_STATUS_OK) + { + return (nRetVal); + } + + xnLogSetMaskMinSeverity(XN_LOG_MASK_ALL, XN_LOG_VERBOSE); + m_writer.Register(); + + XnStatus rc = XnDeviceEnumeration::ConnectedEvent().Register(OnDeviceConnected, this, m_connectedEventHandle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + rc = XnDeviceEnumeration::DisconnectedEvent().Register(OnDeviceDisconnected, this, m_disconnectedEventHandle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + rc = XnDeviceEnumeration::Initialize(); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + return ONI_STATUS_OK; +} + +void XnOniDriver::shutdown() +{ + if (m_connectedEventHandle != NULL) + { + XnDeviceEnumeration::ConnectedEvent().Unregister(m_connectedEventHandle); + m_connectedEventHandle = NULL; + } + + if (m_disconnectedEventHandle != NULL) + { + XnDeviceEnumeration::DisconnectedEvent().Unregister(m_disconnectedEventHandle); + m_disconnectedEventHandle = NULL; + } + + // Close all open devices and release the memory + for (xnl::StringsHash::Iterator it = m_devices.Begin(); it != m_devices.End(); ++it) + { + XN_DELETE(it->Value()); + } + + m_devices.Clear(); + + XnDeviceEnumeration::Shutdown(); +} + +oni::driver::DeviceBase* XnOniDriver::deviceOpen(const char* uri, const char* mode) +{ + XnOniDevice* pDevice = NULL; + + // if device was already opened for this uri, return the previous one + if (m_devices.Get(uri, pDevice) == XN_STATUS_OK) + { + getServices().errorLoggerAppend("Device is already open."); + return NULL; + } + + pDevice = XN_NEW(XnOniDevice, uri, getServices(), this); + XnStatus nRetVal = pDevice->Init(mode); + if (nRetVal != XN_STATUS_OK) + { + getServices().errorLoggerAppend("Could not open \"%s\": %s", uri, xnGetStatusString(nRetVal)); + return NULL; + } + + // Register to error state property changed. + XnCallbackHandle handle; + nRetVal = pDevice->GetSensor()->RegisterToPropertyChange(XN_MODULE_NAME_DEVICE, + XN_MODULE_PROPERTY_ERROR_STATE, + OnDevicePropertyChanged, pDevice, handle); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pDevice); + return NULL; + } + + // Add the device and return it. + m_devices[uri] = pDevice; + return pDevice; +} + +void XnOniDriver::deviceClose(oni::driver::DeviceBase* pDevice) +{ + for (xnl::StringsHash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (iter->Value() == pDevice) + { + m_devices.Remove(iter); + XN_DELETE(pDevice); + return; + } + } + + // not our device?! + XN_ASSERT(FALSE); +} + +void* XnOniDriver::enableFrameSync(oni::driver::StreamBase** pStreams, int streamCount) +{ + // Make sure all the streams belong to same device. + XnOniDevice* pDevice = NULL; + for (int i = 0; i < streamCount; ++i) + { + XnOniStream* pStream = dynamic_cast(pStreams[i]); + if (pStreams == NULL) + { + // Not allowed. + return NULL; + } + + // Check if device was not set before. + if (pDevice == NULL) + { + pDevice = pStream->GetDevice(); + } + // Compare device to stream's device. + else if (pDevice != pStream->GetDevice()) + { + // Streams from different devices are currently not allowed. + return NULL; + } + } + + // Create the frame sync group handle. + FrameSyncGroup* pFrameSyncGroup = XN_NEW(FrameSyncGroup); + if (pFrameSyncGroup == NULL) + { + return NULL; + } + pFrameSyncGroup->pDevice = pDevice; + + // Enable the frame sync. + OniStatus rc = pDevice->EnableFrameSync((XnOniStream**)pStreams, streamCount); + if (rc != ONI_STATUS_OK) + { + XN_DELETE(pFrameSyncGroup); + return NULL; + } + + // Return the created handle. + return pFrameSyncGroup; +} + +void XnOniDriver::disableFrameSync(void* frameSyncGroup) +{ + FrameSyncGroup* pFrameSyncGroup = (FrameSyncGroup*)frameSyncGroup; + + // Find device in driver. + xnl::StringsHash::ConstIterator iter = m_devices.Begin(); + while (iter != m_devices.End()) + { + // Make sure device belongs to driver. + if ((*iter).second == pFrameSyncGroup->pDevice) + { + // Disable frame sync in device. + pFrameSyncGroup->pDevice->DisableFrameSync(); + return; + } + ++iter; + } +} + +void XN_CALLBACK_TYPE XnOniDriver::OnDevicePropertyChanged(const XnChar* ModuleName, XnUInt32 nPropertyId, void* pCookie) +{ + XnOniDevice* pDevice = (XnOniDevice*)pCookie; + XnOniDriver* pThis = pDevice->GetDriver(); + + if (nPropertyId == XN_MODULE_PROPERTY_ERROR_STATE) + { + XnSensor* pSensor = (XnSensor*)pDevice->GetSensor(); + + // Get the property value. + XnUInt64 errorState = 0; + XnStatus nRetVal = pSensor->GetProperty(ModuleName, XN_MODULE_PROPERTY_ERROR_STATE, &errorState); + if (nRetVal == XN_STATUS_OK) + { + if (errorState == XN_STATUS_DEVICE_NOT_CONNECTED) + { + pThis->deviceDisconnected(pDevice->GetInfo()); + } + else + { + int errorStateValue = XN_ERROR_STATE_OK; + switch (errorState) + { + case XN_STATUS_OK: + { + errorStateValue = XN_ERROR_STATE_OK; + break; + } + case XN_STATUS_DEVICE_PROJECTOR_FAULT: + { + errorStateValue = XN_ERROR_STATE_DEVICE_PROJECTOR_FAULT; + break; + } + case XN_STATUS_DEVICE_OVERHEAT: + { + errorStateValue = XN_ERROR_STATE_DEVICE_OVERHEAT; + break; + } + default: + { + // Invalid value. + XN_ASSERT(FALSE); + } + } + pThis->deviceStateChanged(pDevice->GetInfo(), errorStateValue); + } + } + } +} + +void XN_CALLBACK_TYPE XnOniDriver::OnDeviceConnected(const OniDeviceInfo& deviceInfo, void* pCookie) +{ + XnOniDriver* pThis = (XnOniDriver*)pCookie; + pThis->deviceConnected(&deviceInfo); +} + +void XN_CALLBACK_TYPE XnOniDriver::OnDeviceDisconnected(const OniDeviceInfo& deviceInfo, void* pCookie) +{ + XnOniDriver* pThis = (XnOniDriver*)pCookie; + pThis->deviceDisconnected(&deviceInfo); +} diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniDriver.h b/Source/Drivers/PS1080/DriverImpl/XnOniDriver.h new file mode 100644 index 0000000..8283651 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniDriver.h @@ -0,0 +1,84 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ONI_DRIVER_H__ +#define __XN_ONI_DRIVER_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include "XnOniDevice.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnOniDriver : + public oni::driver::DriverBase +{ +public: + XnOniDriver(OniDriverServices* pDriverServices) : DriverBase(pDriverServices), m_writer(pDriverServices), m_connectedEventHandle(NULL), m_disconnectedEventHandle(NULL) + {} + + virtual OniStatus initialize(oni::driver::DeviceConnectedCallback deviceConnectedCallback, oni::driver::DeviceDisconnectedCallback deviceDisconnectedCallback, oni::driver::DeviceStateChangedCallback deviceStateChangedCallback, void* pCookie); + virtual void shutdown(); + + virtual oni::driver::DeviceBase* deviceOpen(const char* uri, const char* mode); + virtual void deviceClose(oni::driver::DeviceBase* pDevice); + + virtual void* enableFrameSync(oni::driver::StreamBase** pStreams, int streamCount); + virtual void disableFrameSync(void* frameSyncGroup); + + void ClearDevice(const char* uri); + +protected: + static void XN_CALLBACK_TYPE OnDevicePropertyChanged(const XnChar* ModuleName, XnUInt32 nPropertyId, void* pCookie); + static void XN_CALLBACK_TYPE OnDeviceConnected(const OniDeviceInfo& deviceInfo, void* pCookie); + static void XN_CALLBACK_TYPE OnDeviceDisconnected(const OniDeviceInfo& deviceInfo, void* pCookie); + + //uri -> XnOniDevice map + xnl::StringsHash m_devices; + +private: + class XnOpenNILogWriter : public XnLogWriterBase + { + public: + XnOpenNILogWriter(OniDriverServices* pDriverServices); + virtual void WriteEntry(const XnLogEntry* pEntry); + virtual void WriteUnformatted(const XnChar* strMessage); + + private: + OniDriverServices* m_pDriverServices; + }; + + typedef struct + { + XnOniDevice* pDevice; + } FrameSyncGroup; + + XnOpenNILogWriter m_writer; + XnCallbackHandle m_connectedEventHandle; + XnCallbackHandle m_disconnectedEventHandle; +}; + +#endif // __XN_ONI_DRIVER_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniIRStream.cpp b/Source/Drivers/PS1080/DriverImpl/XnOniIRStream.cpp new file mode 100644 index 0000000..413b5fb --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniIRStream.cpp @@ -0,0 +1,35 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniIRStream.h" + +//--------------------------------------------------------------------------- +// XnOniIRStream class +//--------------------------------------------------------------------------- + +XnOniIRStream::XnOniIRStream(XnSensor* pSensor, XnOniDevice* pDevice) : + XnOniMapStream(pSensor, XN_STREAM_TYPE_IR, ONI_SENSOR_IR, pDevice) +{ +} + + diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniIRStream.h b/Source/Drivers/PS1080/DriverImpl/XnOniIRStream.h new file mode 100644 index 0000000..52a42b4 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniIRStream.h @@ -0,0 +1,40 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ONI_IR_STREAM_H__ +#define __XN_ONI_IR_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniMapStream.h" +#include "../Sensor/XnSensor.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnOniIRStream : + public XnOniMapStream +{ +public: + XnOniIRStream(XnSensor* pSensor, XnOniDevice* pDevice); +}; + +#endif // __XN_ONI_IR_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniMapStream.cpp b/Source/Drivers/PS1080/DriverImpl/XnOniMapStream.cpp new file mode 100644 index 0000000..f643cec --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniMapStream.cpp @@ -0,0 +1,365 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniMapStream.h" +#include "XnOniColorStream.h" +#include + +//--------------------------------------------------------------------------- +// XnOniMapStream class +//--------------------------------------------------------------------------- + +XnOniMapStream::XnOniMapStream(XnSensor* pSensor, const XnChar* strName, OniSensorType sensorType, XnOniDevice* pDevice) : + XnOniStream(pSensor, strName, sensorType, pDevice), + m_nSupportedModesCount(0), + m_aSupportedModes(NULL) +{ +} + +XnOniMapStream::~XnOniMapStream() +{ + if (m_aSupportedModes != NULL) + { + XN_DELETE_ARR(m_aSupportedModes); + m_aSupportedModes = NULL; + } +} + +XnStatus XnOniMapStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnOniStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = FillSupportedVideoModes(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +OniStatus XnOniMapStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + //TODO: reconsider return values translation issues + XnStatus nRetVal = XN_STATUS_ERROR; + + switch(propertyId) + { + case ONI_STREAM_PROPERTY_VIDEO_MODE: + { + if (*pDataSize != sizeof(OniVideoMode)) + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Unexpected size: %d != %d", *pDataSize, sizeof(OniVideoMode)); + return ONI_STATUS_ERROR; + } + + nRetVal = GetVideoMode((OniVideoMode*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + } + case ONI_STREAM_PROPERTY_MIRRORING: + { + if (*pDataSize != sizeof(OniBool)) + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Unexpected size: %d != %d", *pDataSize, sizeof(OniBool)); + return ONI_STATUS_ERROR; + } + + nRetVal = GetMirror((OniBool*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + } + case ONI_STREAM_PROPERTY_CROPPING: + { + if (*pDataSize != sizeof(OniCropping)) + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Unexpected size: %d != %d", *pDataSize, sizeof(OniCropping)); + return ONI_STATUS_ERROR; + } + + nRetVal = GetCropping(*(OniCropping*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + } + default: + { + return XnOniStream::getProperty(propertyId, data, pDataSize); + } + } + + return ONI_STATUS_OK; +} + +XnStatus XnOniMapStream::SetPropertyImpl(int propertyId, const void* data, int dataSize) +{ + XnStatus nRetVal = XN_STATUS_ERROR; + + switch(propertyId) + { + case ONI_STREAM_PROPERTY_VIDEO_MODE: + { + if (dataSize != sizeof(OniVideoMode)) + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Unexpected size: %d != %d", dataSize, sizeof(OniVideoMode)); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + nRetVal = SetVideoMode((OniVideoMode*)data); + XN_IS_STATUS_OK(nRetVal); + + break; + } + case ONI_STREAM_PROPERTY_MIRRORING: + { + if (dataSize != sizeof(OniBool)) + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Unexpected size: %d != %d", dataSize, sizeof(OniBool)); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + nRetVal = SetMirror((OniBool*)data); + XN_IS_STATUS_OK(nRetVal); + + break; + } + case ONI_STREAM_PROPERTY_CROPPING: + { + if (dataSize != sizeof(OniCropping)) + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Unexpected size: %d != %d", dataSize, sizeof(OniCropping)); + return XN_STATUS_DEVICE_PROPERTY_BAD_TYPE; + } + + nRetVal = SetCropping(*(OniCropping*)data); + XN_IS_STATUS_OK(nRetVal); + + break; + } + default: + { + return XnOniStream::SetPropertyImpl(propertyId, data, dataSize); + } + } + + return XN_STATUS_OK; +} + +OniBool XnOniMapStream::isPropertySupported(int propertyId) +{ + return ( + propertyId == ONI_STREAM_PROPERTY_VIDEO_MODE || + propertyId == ONI_STREAM_PROPERTY_MIRRORING || + propertyId == ONI_STREAM_PROPERTY_CROPPING || + XnOniStream::isPropertySupported(propertyId)); +} + +void XnOniMapStream::notifyAllProperties() +{ + XnOniStream::notifyAllProperties(); + + XnUInt32 nValue; + int size = sizeof(nValue); + + // input format + getProperty(XN_STREAM_PROPERTY_INPUT_FORMAT, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_INPUT_FORMAT, &nValue, size); + + // cropping mode + getProperty(XN_STREAM_PROPERTY_CROPPING_MODE, &nValue, &size); + raisePropertyChanged(XN_STREAM_PROPERTY_CROPPING_MODE, &nValue, size); +} + +XnStatus XnOniMapStream::GetVideoMode(OniVideoMode* pVideoMode) +{ + XnStatus nRetVal; + XnUInt64 nValue; + + // output format + nRetVal = m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_OUTPUT_FORMAT, &nValue); + XN_IS_STATUS_OK(nRetVal); + pVideoMode->pixelFormat = (OniPixelFormat)nValue; + + // resolution + nRetVal = m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_X_RES, &nValue); + XN_IS_STATUS_OK(nRetVal); + pVideoMode->resolutionX = (int)nValue; + nRetVal = m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_Y_RES, &nValue); + XN_IS_STATUS_OK(nRetVal); + pVideoMode->resolutionY = (int)nValue; + + // fps + nRetVal = m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_FPS, &nValue); + XN_IS_STATUS_OK(nRetVal); + pVideoMode->fps = (int)nValue; + + return XN_STATUS_OK; +} + +XnBool EqualsResFPS(const OniVideoMode* mode1, const OniVideoMode* mode2) +{ + return ( + mode1->resolutionX == mode2->resolutionX && + mode1->resolutionY == mode2->resolutionY && + mode1->fps == mode2->fps + ); +} + +XnStatus XnOniMapStream::SetVideoMode(OniVideoMode* pVideoMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + OniVideoMode current; + GetVideoMode(¤t); + + if (!xnOSMemCmp(¤t, pVideoMode, sizeof(OniVideoMode))) + { + // nothing to do here + return (ONI_STATUS_OK); + } + + // check if this mode is supported. If it is, make sure current input format is OK + // note: in case of color stream, the chosen InputFormat should also suit the desired OutputFormat! + XnUInt64 nCurrInputFormat; + nRetVal = m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_INPUT_FORMAT, &nCurrInputFormat); + XN_IS_STATUS_OK(nRetVal); + + XnUInt32 nChosenInputFormat = XN_MAX_UINT32; + + for (XnUInt32 i = 0; i < m_nSupportedModesCount; ++i) + { + if (EqualsResFPS(pVideoMode, &m_aSupportedModes[i].OutputMode)) + { + // if current input format is supported, it will always be preferred. + if (m_aSupportedModes[i].nInputFormat == nCurrInputFormat) + { + if(m_sensorType == ONI_SENSOR_COLOR && + XnOniColorStream::IsSupportedInputFormat((XnIOImageFormats)nCurrInputFormat, pVideoMode->pixelFormat) == FALSE) + { + continue; + } + nChosenInputFormat = (XnUInt32)nCurrInputFormat; + break; + } + else if (nChosenInputFormat == XN_MAX_UINT32 || + (m_sensorType == ONI_SENSOR_COLOR && XnOniColorStream::IsPreferredInputFormat( + (XnIOImageFormats)m_aSupportedModes[i].nInputFormat, + (XnIOImageFormats)nChosenInputFormat, + pVideoMode->pixelFormat))) + { + nChosenInputFormat = m_aSupportedModes[i].nInputFormat; + // don't break yet. we might find our input format + } + } + } + + if (nChosenInputFormat == XN_MAX_UINT32) // not found + { + xnLogWarning(XN_MASK_DEVICE_SENSOR, "Mode %ux%u@%u is not supported!", pVideoMode->resolutionX, pVideoMode->resolutionY, pVideoMode->fps); + return XN_STATUS_BAD_PARAM; + } + + XN_PROPERTY_SET_CREATE_ON_STACK(props); + XnPropertySetAddModule(&props, m_strType); + XnPropertySetAddIntProperty(&props, m_strType, XN_STREAM_PROPERTY_X_RES, pVideoMode->resolutionX); + XnPropertySetAddIntProperty(&props, m_strType, XN_STREAM_PROPERTY_Y_RES, pVideoMode->resolutionY); + XnPropertySetAddIntProperty(&props, m_strType, XN_STREAM_PROPERTY_FPS, pVideoMode->fps); + + if (nChosenInputFormat != nCurrInputFormat) + { + XnPropertySetAddIntProperty(&props, m_strType, XN_STREAM_PROPERTY_INPUT_FORMAT, nChosenInputFormat); + } + // set the remotely desired output format + XnPropertySetAddIntProperty(&props, m_strType, XN_STREAM_PROPERTY_OUTPUT_FORMAT, pVideoMode->pixelFormat); + + nRetVal = m_pSensor->BatchConfig(&props); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnOniMapStream::FillSupportedVideoModes() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get supported modes + XnUInt64 nCount; + nRetVal = m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_SUPPORT_MODES_COUNT, &nCount); + XN_IS_STATUS_OK(nRetVal); + + m_aSupportedModes = XN_NEW_ARR(SupportedVideoMode, (int)nCount); + XN_VALIDATE_ALLOC_PTR(m_aSupportedModes); + m_nSupportedModesCount = (XnUInt32)nCount; + + const XnUInt32 nAllocCount = 150; + XnCmosPreset aPresets[nAllocCount]; + XN_ASSERT(nAllocCount >= m_nSupportedModesCount); + OniGeneralBuffer gb = XnGeneralBufferPack(aPresets, m_nSupportedModesCount * sizeof(XnCmosPreset)); + nRetVal = m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_SUPPORT_MODES, gb); + XN_IS_STATUS_OK(nRetVal); + + // Keep those modes + XnBool bOK = TRUE; + for (XnUInt32 i = 0; i < m_nSupportedModesCount; ++i) + { + m_aSupportedModes[i].nInputFormat = aPresets[i].nFormat; + bOK = XnDDKGetXYFromResolution((XnResolutions)aPresets[i].nResolution, (XnUInt32*)&m_aSupportedModes[i].OutputMode.resolutionX, (XnUInt32*)&m_aSupportedModes[i].OutputMode.resolutionY); + XN_ASSERT(bOK); + XN_REFERENCE_VARIABLE(bOK); + m_aSupportedModes[i].OutputMode.fps = aPresets[i].nFPS; + m_aSupportedModes[i].OutputMode.pixelFormat = (OniPixelFormat)-1; // this field is not to be used here. + } + + return (XN_STATUS_OK); +} + +XnStatus XnOniMapStream::GetMirror(OniBool* pEnabled) +{ + XnUInt64 intProperty; + XnStatus nRetVal = m_pSensor->GetProperty(m_strType, XN_MODULE_PROPERTY_MIRROR, &intProperty); + XN_IS_STATUS_OK(nRetVal); + + // Convert the received value back to OniBool. + *pEnabled = (OniBool)intProperty; + + return (XN_STATUS_OK); +} + +XnStatus XnOniMapStream::SetMirror(OniBool* pEnabled) +{ + XnStatus nRetVal = m_pSensor->SetProperty(m_strType, XN_MODULE_PROPERTY_MIRROR, (XnUInt64)*pEnabled); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnOniMapStream::GetCropping(OniCropping &cropping) +{ + return m_pSensor->GetProperty(m_strType, XN_STREAM_PROPERTY_CROPPING, XN_PACK_GENERAL_BUFFER(cropping)); +} + +XnStatus XnOniMapStream::SetCropping(const OniCropping &cropping) +{ + OniGeneralBuffer gbValue = XnGeneralBufferPack((void*)&cropping, sizeof(cropping)); + return m_pSensor->SetProperty(m_strType, XN_STREAM_PROPERTY_CROPPING, gbValue); +} + diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniMapStream.h b/Source/Drivers/PS1080/DriverImpl/XnOniMapStream.h new file mode 100644 index 0000000..924fbe8 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniMapStream.h @@ -0,0 +1,69 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ONI_MAP_STREAM_H__ +#define __XN_ONI_MAP_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniStream.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnOniMapStream : + public XnOniStream +{ +public: + XnOniMapStream(XnSensor* pSensor, const XnChar* strName, OniSensorType sensorType, XnOniDevice* pDevice); + virtual ~XnOniMapStream(); + + virtual XnStatus Init(); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual XnStatus SetPropertyImpl(int propertyId, const void* data, int dataSize); + virtual OniBool isPropertySupported(int propertyId); + virtual void notifyAllProperties(); + + XnStatus GetVideoMode(OniVideoMode* pVideoMode); + XnStatus SetVideoMode(OniVideoMode* pVideoMode); + + XnStatus GetMirror(OniBool* pEnabled); + XnStatus SetMirror(OniBool* pEnabled); + + XnStatus GetCropping(OniCropping &cropping); + XnStatus SetCropping(const OniCropping &cropping); + +protected: + struct SupportedVideoMode + { + OniVideoMode OutputMode; + XnUInt32 nInputFormat; + }; + + XnUInt32 m_nSupportedModesCount; + SupportedVideoMode* m_aSupportedModes; + +private: + XnStatus FillSupportedVideoModes(); +}; + +#endif // __XN_ONI_MAP_STREAM_H__ diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniStream.cpp b/Source/Drivers/PS1080/DriverImpl/XnOniStream.cpp new file mode 100644 index 0000000..657f859 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniStream.cpp @@ -0,0 +1,178 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnOniStream.h" +#include "DDK/XnFrameStream.h" + +//--------------------------------------------------------------------------- +// XnOniStream class +//--------------------------------------------------------------------------- +XnOniStream::XnOniStream(XnSensor* pSensor, const XnChar* strName, OniSensorType sensorType, XnOniDevice* pDevice) : + m_sensorType(sensorType), + m_pSensor(pSensor), + m_strType(strName), + m_pDevice(pDevice), + m_started(FALSE) +{ +} + +XnOniStream::~XnOniStream() +{ + destroy(); +} + +XnStatus XnOniStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_pSensor->CreateStream(m_strType, m_strType, NULL); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_pSensor->RegisterToNewStreamData(OnNewStreamDataEventHandler, this, m_hNewDataCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_pSensor->GetStream(m_strType, &m_pDeviceStream); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnOniStream::destroy() +{ + stop(); + m_pSensor->UnregisterFromNewStreamData(m_hNewDataCallback); + m_pSensor->DestroyStream(m_strType); +} + +void XnOniStream::setServices(oni::driver::StreamServices* pStreamServices) +{ + oni::driver::StreamBase::setServices(pStreamServices); + m_pDeviceStream->SetServices(*pStreamServices); +} + +OniStatus XnOniStream::start() +{ + XnStatus nRetVal = ONI_STATUS_OK; + + if (!m_started) + { + // first lock the stream (so that IsOpen() and Open() will be an atomic action) + xnl::AutoCSLocker locker(*m_pDeviceStream->GetOpenLock()); + + if (m_pDeviceStream->IsOpen()) + { + m_pDeviceStream->OpenAddRef(); + } + else + { + nRetVal = m_pDeviceStream->Open(); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + } + + m_started = TRUE; + } + + return ONI_STATUS_OK; +} + +void XnOniStream::stop() +{ + if (m_started) + { + m_started = FALSE; + + // first lock the stream (so that OpenDecRef() and Close() will be an atomic action) + xnl::AutoCSLocker locker(*m_pDeviceStream->GetOpenLock()); + + if (0 == m_pDeviceStream->OpenDecRef()) + { + m_pDeviceStream->Close(); + } + } +} + +OniStatus XnOniStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + XnStatus nRetVal = m_pDeviceStream->GetProperty(propertyId, data, pDataSize); + if (nRetVal != XN_STATUS_OK) + { + return ONI_STATUS_BAD_PARAMETER; + } + return ONI_STATUS_OK; +} + +OniStatus XnOniStream::setProperty(int propertyId, const void* data, int dataSize) +{ + xnl::AutoCSLocker locker(*m_pDeviceStream->GetOpenLock()); + + // if this stream is open, and not just by me (multiple depth streams for example), don't allow any changes + XnUInt32 myOpenRefCount = m_started ? 1 : 0; + if (m_pDeviceStream->GetOpenRefCount() > myOpenRefCount) + { + return ONI_STATUS_OUT_OF_FLOW; + } + else + { + XnStatus nRetVal = SetPropertyImpl(propertyId, data, dataSize); + switch(nRetVal) + { + case XN_STATUS_OK: + return ONI_STATUS_OK; + + case XN_STATUS_DEVICE_PROPERTY_BAD_TYPE: + return ONI_STATUS_BAD_PARAMETER; + + case XN_STATUS_BAD_PARAM: + case XN_STATUS_DEVICE_BAD_PARAM: + default: + return ONI_STATUS_NOT_SUPPORTED; + } + } +} + +XnStatus XnOniStream::SetPropertyImpl(int propertyId, const void* data, int dataSize) +{ + return m_pDeviceStream->SetProperty(propertyId, data, dataSize); +} + +OniBool XnOniStream::isPropertySupported(int propertyId) +{ + XnBool exists; + m_pDeviceStream->DoesPropertyExist(propertyId, &exists); + return (exists == TRUE); +} + +void XN_CALLBACK_TYPE XnOniStream::OnNewStreamDataEventHandler(const XnNewStreamDataEventArgs& args, void* pCookie) +{ + XnOniStream* pThis = (XnOniStream*)pCookie; + if (pThis->m_started && strcmp(args.strStreamName, pThis->m_strType) == 0) + { + pThis->raiseNewFrame(args.pFrame); + } +} + +int XnOniStream::getRequiredFrameSize() +{ + return m_pDeviceStream->GetRequiredDataSize(); +} + diff --git a/Source/Drivers/PS1080/DriverImpl/XnOniStream.h b/Source/Drivers/PS1080/DriverImpl/XnOniStream.h new file mode 100644 index 0000000..176bee0 --- /dev/null +++ b/Source/Drivers/PS1080/DriverImpl/XnOniStream.h @@ -0,0 +1,77 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_ONI_STREAM_H__ +#define __XN_ONI_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include "../Sensor/XnSensor.h" + +//using namespace oni::driver; +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnDeviceStream; +class XnOniDevice; + +class XnOniStream : + public oni::driver::StreamBase +{ +public: + XnOniStream(XnSensor* pSensor, const XnChar* strName, OniSensorType sensorType, XnOniDevice* pDevice); + ~XnOniStream(); + + virtual XnStatus Init(); + + virtual void setServices(oni::driver::StreamServices* pStreamServices); + + OniStatus start(); + void stop(); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + virtual OniBool isPropertySupported(int propertyId); + + virtual int getRequiredFrameSize(); + + XnOniDevice* GetDevice() { return m_pDevice; } + XnDeviceStream* GetDeviceStream() { return m_pDeviceStream; } + +protected: + virtual XnStatus SetPropertyImpl(int propertyId, const void* data, int dataSize); + + OniSensorType m_sensorType; + XnSensor* m_pSensor; + const XnChar* m_strType; + XnDeviceStream* m_pDeviceStream; + XnOniDevice* m_pDevice; + XnCallbackHandle m_hNewDataCallback; + +private: + void destroy(); + XnBool m_started; + static void XN_CALLBACK_TYPE OnNewStreamDataEventHandler(const XnNewStreamDataEventArgs& args, void* pCookie); +}; + +#endif // __XN_ONI_STREAM_H__ diff --git a/Source/Drivers/PS1080/Formats/Xn16zCodec.h b/Source/Drivers/PS1080/Formats/Xn16zCodec.h new file mode 100644 index 0000000..190c7f8 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/Xn16zCodec.h @@ -0,0 +1,53 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_16Z_CODEC_H__ +#define __XN_16Z_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class Xn16zCodec : public XnCodecBase +{ +public: + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_16Z; } + + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_DEPTH16Z_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + return XnStreamCompressDepth16Z((XnUInt16*)pData, nDataSize, pCompressedData, pnCompressedDataSize); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressDepth16Z(pCompressedData, nCompressedDataSize, (XnUInt16*)pData, pnDataSize); + } +}; + +#endif //__XN_16Z_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Formats/Xn16zEmbTablesCodec.h b/Source/Drivers/PS1080/Formats/Xn16zEmbTablesCodec.h new file mode 100644 index 0000000..91be2b3 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/Xn16zEmbTablesCodec.h @@ -0,0 +1,58 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_16Z_EMB_TABLES_CODEC_H__ +#define __XN_16Z_EMB_TABLES_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class Xn16zEmbTablesCodec : public XnCodecBase +{ +public: + Xn16zEmbTablesCodec(XnUInt16 nMaxValue) : m_nMaxValue(nMaxValue) {} + + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_16Z_EMB_TABLE; } + + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_DEPTH16Z_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return m_nMaxValue * sizeof(XnUInt16); } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + return XnStreamCompressDepth16ZWithEmbTable((XnUInt16*)pData, nDataSize, pCompressedData, pnCompressedDataSize, m_nMaxValue); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressDepth16ZWithEmbTable(pCompressedData, nCompressedDataSize, (XnUInt16*)pData, pnDataSize); + } + +private: + XnUInt16 m_nMaxValue; +}; + +#endif //__XN_16Z_EMB_TABLES_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Formats/Xn8zCodec.h b/Source/Drivers/PS1080/Formats/Xn8zCodec.h new file mode 100644 index 0000000..c799e35 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/Xn8zCodec.h @@ -0,0 +1,52 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_8Z_CODEC_H__ +#define __XN_8Z_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class Xn8zCodec : public XnCodecBase +{ +public: + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_COLOR_8Z; } + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_IMAGE8Z_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + return XnStreamCompressImage8Z(pData, nDataSize, pCompressedData, pnCompressedDataSize); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressImage8Z(pCompressedData, nCompressedDataSize, pData, pnDataSize); + } +}; + +#endif //__XN_8Z_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Formats/XnCodec.h b/Source/Drivers/PS1080/Formats/XnCodec.h new file mode 100644 index 0000000..3d51f35 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnCodec.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_CODEC_H__ +#define __XN_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include "XnFormats.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef XnUInt32 XnCodecID; + +class XnCodec +{ +public: + static XnCompressionFormats GetCompressionFormatFromCodecID(XnCodecID codecID); + static XnCodecID GetCodecIDFromCompressionFormat(XnCompressionFormats format); + + XnCodec() {} + virtual ~XnCodec() {} + + virtual XnStatus Init() { return XN_STATUS_OK; } + + virtual XnCompressionFormats GetCompressionFormat() const = 0; + + virtual XnStatus Compress(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) = 0; + + virtual XnStatus Decompress(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) = 0; +}; + +#endif //__XN_CODEC_H__ diff --git a/Source/Drivers/PS1080/Formats/XnCodecBase.h b/Source/Drivers/PS1080/Formats/XnCodecBase.h new file mode 100644 index 0000000..71c6774 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnCodecBase.h @@ -0,0 +1,86 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_CODEC_BASE_H__ +#define __XN_CODEC_BASE_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodec.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnCodecBase : public XnCodec +{ +public: + static XnCompressionFormats GetCompressionFormatFromCodecID(XnCodecID codecID); + static XnCodecID GetCodecIDFromCompressionFormat(XnCompressionFormats format); + + XnCodecBase() {} + virtual ~XnCodecBase() {} + + virtual XnStatus Init() { return XN_STATUS_OK; } + + virtual XnCompressionFormats GetCompressionFormat() const = 0; + + XnStatus Compress(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pData); + XN_VALIDATE_INPUT_PTR(pCompressedData); + XN_VALIDATE_OUTPUT_PTR(pnCompressedDataSize); + + if ((nDataSize * GetWorseCompressionRatio() + GetOverheadSize()) > *pnCompressedDataSize) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + nRetVal = CompressImpl(pData, nDataSize, pCompressedData, pnCompressedDataSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); + } + + XnStatus Decompress(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(pCompressedData); + XN_VALIDATE_INPUT_PTR(pData); + XN_VALIDATE_OUTPUT_PTR(pnDataSize); + + nRetVal = DecompressImpl(pCompressedData, nCompressedDataSize, pData, pnDataSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); + } + + virtual XnUInt32 GetOverheadSize() const = 0; + virtual XnFloat GetWorseCompressionRatio() const = 0; + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) = 0; + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) = 0; +}; + +#endif // __XN_CODEC_BASE_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Formats/XnCodecs.cpp b/Source/Drivers/PS1080/Formats/XnCodecs.cpp new file mode 100644 index 0000000..db6798b --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnCodecs.cpp @@ -0,0 +1,68 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnUncompressedCodec.h" +#include "Xn16zCodec.h" +#include "Xn16zEmbTablesCodec.h" +#include "Xn8zCodec.h" +#include "XnJpegCodec.h" +#include "XnNiCodec.h" + +XnCompressionFormats XnCodec::GetCompressionFormatFromCodecID(XnCodecID /*codecID*/) +{ + /*switch (codecID) + {*/ + //case XN_CODEC_UNCOMPRESSED: + // return XN_COMPRESSION_NONE; + //case XN_CODEC_16Z: + // return XN_COMPRESSION_16Z; + //case XN_CODEC_16Z_EMB_TABLES: + // return XN_COMPRESSION_16Z_EMB_TABLE; + //case XN_CODEC_8Z: + // return XN_COMPRESSION_COLOR_8Z; + //case XN_CODEC_JPEG: + // return XN_COMPRESSION_JPEG; + //default: + return (XnCompressionFormats)-1; + //} +} + +XnCodecID XnCodec::GetCodecIDFromCompressionFormat(XnCompressionFormats /*format*/) +{ + //switch (format) + //{ + //case XN_COMPRESSION_16Z: + // return XN_CODEC_16Z; + //case XN_COMPRESSION_16Z_EMB_TABLE: + // return XN_CODEC_16Z_EMB_TABLES; + //case XN_COMPRESSION_JPEG: + // return XN_CODEC_JPEG; + //case XN_COMPRESSION_NONE: + // return XN_CODEC_UNCOMPRESSED; + //case XN_COMPRESSION_COLOR_8Z: + // return XN_CODEC_8Z; + //default: + //return XN_CODEC_NULL; + return (XnCodecID)-1; + //} +} diff --git a/Source/Drivers/PS1080/Formats/XnFormats.cpp b/Source/Drivers/PS1080/Formats/XnFormats.cpp new file mode 100644 index 0000000..e0409f8 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnFormats.cpp @@ -0,0 +1,34 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFormats.h" +#include +#include + +//--------------------------------------------------------------------------- +// Global Variables +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- diff --git a/Source/Drivers/PS1080/Formats/XnFormats.h b/Source/Drivers/PS1080/Formats/XnFormats.h new file mode 100644 index 0000000..a84bd32 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnFormats.h @@ -0,0 +1,51 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_FORMATS_H_ +#define _XN_FORMATS_H_ + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_MASK_FORMATS "XnFormats" + +//--------------------------------------------------------------------------- +// Exported Functions +//--------------------------------------------------------------------------- + +/** +* This function receives a buffer of pixel data of a known format, and mirrors it. +* +* @param nOutputFormat [in] The format of the pixel data. +* @param pBuffer [in] A pointer to the buffer. +* @param nBufferSize [in] The size of the buffer, in bytes. +* @param nXRes [in] X-resolution (line size in pixels) of the buffer. +*/ +XnStatus XnFormatsMirrorPixelData(OniPixelFormat nOutputFormat, XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt32 nXRes); + +#endif //_XN_FORMATS_H_ diff --git a/Source/Drivers/PS1080/Formats/XnFormatsMirror.cpp b/Source/Drivers/PS1080/Formats/XnFormatsMirror.cpp new file mode 100644 index 0000000..120534c --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnFormatsMirror.cpp @@ -0,0 +1,242 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include "XnFormats.h" +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_MIRROR_MAX_LINE_SIZE 1920*3 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnMirrorOneBytePixels(XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt32 nLineSize) +{ + // Local function variables + XnUInt8* pSrc = pBuffer; + XnUInt8 pLineBuffer[XN_MIRROR_MAX_LINE_SIZE]; + XnUInt8* pSrcEnd = pSrc + nBufferSize; + XnUInt8* pDest = NULL; + XnUInt8* pDestVal = &pLineBuffer[0] + nLineSize - 1; + XnUInt8* pDestEnd = &pLineBuffer[0] - 1; + + if (nLineSize > XN_MIRROR_MAX_LINE_SIZE) + { + return (XN_STATUS_INTERNAL_BUFFER_TOO_SMALL); + } + + while (pSrc < pSrcEnd) + { + xnOSMemCopy(pLineBuffer, pSrc, nLineSize); + + pDest = pDestVal; + while (pDest != pDestEnd) + { + *pSrc = *pDest; + + pSrc++; + pDest--; + } + } + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnMirrorTwoBytePixels(XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt32 nLineSize) +{ + // Local function variables + XnUInt16* pSrc = (XnUInt16*)pBuffer; + XnUInt16 pLineBuffer[XN_MIRROR_MAX_LINE_SIZE]; + XnUInt16* pSrcEnd = pSrc + nBufferSize / sizeof(XnUInt16); + XnUInt16* pDest = NULL; + XnUInt16* pDestVal = &pLineBuffer[0] + nLineSize - 1; + XnUInt16* pDestEnd = &pLineBuffer[0] - 1; + XnUInt16 nMemCpyLineSize = (XnUInt16)(nLineSize * sizeof(XnUInt16)); + XnUInt16 nValue; + + if (nLineSize > XN_MIRROR_MAX_LINE_SIZE) + { + return (XN_STATUS_INTERNAL_BUFFER_TOO_SMALL); + } + + while (pSrc < pSrcEnd) + { + xnOSMemCopy(pLineBuffer, pSrc, nMemCpyLineSize); + + pDest = pDestVal; + while (pDest != pDestEnd) + { + nValue = pDest[0]; + pSrc[0] = nValue; + + pDest--; + pSrc++; + } + } + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnMirrorThreeBytePixels(XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt32 nLineSize) +{ + // Local function variables + XnUInt8* pSrc = pBuffer; + XnUInt8 pLineBuffer[XN_MIRROR_MAX_LINE_SIZE]; + XnUInt8* pSrcEnd = pSrc + nBufferSize; + XnUInt8* pDest = NULL; + XnUInt8* pDestVal = &pLineBuffer[0] + nLineSize * 3 - 1; + XnUInt8* pDestEnd = &pLineBuffer[0] - 1; + XnUInt16 nMemCpyLineSize = (XnUInt16)(nLineSize * 3); + + if (nMemCpyLineSize > XN_MIRROR_MAX_LINE_SIZE) + { + return (XN_STATUS_INTERNAL_BUFFER_TOO_SMALL); + } + + while (pSrc < pSrcEnd) + { + xnOSMemCopy(pLineBuffer, pSrc, nMemCpyLineSize); + + pDest = pDestVal; + while (pDest != pDestEnd) + { + *pSrc = *(pDest-2); + *(pSrc+1) = *(pDest-1); + *(pSrc+2) = *pDest; + + pSrc+=3; + pDest-=3; + } + } + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnMirrorYUV422Pixels(XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt32 nLineSize) +{ + // Local function variables + XnUInt8* pSrc = pBuffer; + XnUInt8 pLineBuffer[XN_MIRROR_MAX_LINE_SIZE]; + XnUInt8* pSrcEnd = (XnUInt8*)pSrc + nBufferSize; + XnUInt8* pDest = NULL; + XnUInt8* pDestVal = &pLineBuffer[(nLineSize/2-1)*sizeof(XnUInt32)]; // last element + XnUInt8* pDestEnd = &pLineBuffer[0]; // first element + XnUInt32 nMemCpyLineSize = nLineSize/2*sizeof(XnUInt32); + + if (nMemCpyLineSize > XN_MIRROR_MAX_LINE_SIZE) + { + return (XN_STATUS_INTERNAL_BUFFER_TOO_SMALL); + } + + while (pSrc < pSrcEnd) + { + xnOSMemCopy(pLineBuffer, pSrc, nMemCpyLineSize); + pDest = pDestVal; + + while (pDest >= pDestEnd) + { + pSrc[0] = pDest[0]; // u + pSrc[1] = pDest[3]; // y1 <-> y2 + pSrc[2] = pDest[2]; // v + pSrc[3] = pDest[1]; // y2 <-> y1 + + pSrc += 4; + pDest -= 4; + } + } + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnMirrorYUYVPixels(XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt32 nLineSize) +{ + // Local function variables + XnUInt8* pSrc = pBuffer; + XnUInt8 pLineBuffer[XN_MIRROR_MAX_LINE_SIZE]; + XnUInt8* pSrcEnd = (XnUInt8*)pSrc + nBufferSize; + XnUInt8* pDest = NULL; + XnUInt8* pDestVal = &pLineBuffer[(nLineSize/2-1)*sizeof(XnUInt32)]; // last element + XnUInt8* pDestEnd = &pLineBuffer[0]; // first element + XnUInt32 nMemCpyLineSize = nLineSize/2*sizeof(XnUInt32); + + if (nMemCpyLineSize > XN_MIRROR_MAX_LINE_SIZE) + { + return (XN_STATUS_INTERNAL_BUFFER_TOO_SMALL); + } + + while (pSrc < pSrcEnd) + { + xnOSMemCopy(pLineBuffer, pSrc, nMemCpyLineSize); + pDest = pDestVal; + + while (pDest >= pDestEnd) + { + pSrc[0] = pDest[2]; // y1 <-> y2 + pSrc[1] = pDest[3]; // u + pSrc[2] = pDest[0]; // y2 <-> y1 + pSrc[3] = pDest[1]; // v + + pSrc += 4; + pDest -= 4; + } + } + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnFormatsMirrorPixelData(OniPixelFormat nOutputFormat, XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt32 nXRes) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pBuffer); + + switch (nOutputFormat) + { + case ONI_PIXEL_FORMAT_SHIFT_9_2: + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + case ONI_PIXEL_FORMAT_DEPTH_100_UM: + case ONI_PIXEL_FORMAT_GRAY16: + return XnMirrorTwoBytePixels(pBuffer, nBufferSize, nXRes); + case ONI_PIXEL_FORMAT_GRAY8: + return XnMirrorOneBytePixels(pBuffer, nBufferSize, nXRes); + case ONI_PIXEL_FORMAT_YUV422: + return XnMirrorYUV422Pixels(pBuffer, nBufferSize, nXRes); + case ONI_PIXEL_FORMAT_YUYV: + return XnMirrorYUYVPixels(pBuffer, nBufferSize, nXRes); + case ONI_PIXEL_FORMAT_RGB888: + return XnMirrorThreeBytePixels(pBuffer, nBufferSize, nXRes); + default: + xnLogError(XN_MASK_FORMATS, "Mirror was not implemented for output format %d", nOutputFormat); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } +} diff --git a/Source/Drivers/PS1080/Formats/XnFormatsStatus.cpp b/Source/Drivers/PS1080/Formats/XnFormatsStatus.cpp new file mode 100644 index 0000000..6fa12df --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnFormatsStatus.cpp @@ -0,0 +1,27 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +// registration is done by including XnStatusRegister *before* including the list of errors +#include +#define XN_MESSAGE_MAP_REGISTER +#include "XnFormatsStatus.h" diff --git a/Source/Drivers/PS1080/Formats/XnJpegCodec.h b/Source/Drivers/PS1080/Formats/XnJpegCodec.h new file mode 100644 index 0000000..dfd9298 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnJpegCodec.h @@ -0,0 +1,96 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_JPEG_CODEC_H__ +#define __XN_JPEG_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnJpegCodec : public XnCodecBase +{ +public: + XnJpegCodec(XnBool bRGB, XnUInt32 nXRes, XnUInt32 nYRes, XnUInt32 nQuality = XN_STREAM_COMPRESSION_JPEG_DEFAULT_QUALITY) : + m_bRGB(bRGB), m_nXRes(nXRes), m_nYRes(nYRes), m_nQuality(nQuality) + {} + + ~XnJpegCodec() + { + XnStreamFreeCompressImageJ(&m_CompJPEGContext); + XnStreamFreeUncompressImageJ(&m_UncompJPEGContext); + } + + XnStatus Init() + { + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnStreamInitCompressImageJ(&m_CompJPEGContext); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnStreamInitUncompressImageJ(&m_UncompJPEGContext); + if (nRetVal != XN_STATUS_OK) + { + XnStreamFreeCompressImageJ(&m_CompJPEGContext); + return (nRetVal); + } + + return (XN_STATUS_OK); + } + + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_JPEG; } + virtual XnFloat GetWorseCompressionRatio() const { return XN_STREAM_COMPRESSION_IMAGEJ_WORSE_RATIO; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + XN_DISABLE_COPY_AND_ASSIGN(XnJpegCodec); + + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 /*nDataSize*/, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + if (m_bRGB) + { + return XnStreamCompressImage24J(&m_CompJPEGContext, pData, pCompressedData, pnCompressedDataSize, m_nXRes, m_nYRes, m_nQuality); + } + else + { + return XnStreamCompressImage8J(&m_CompJPEGContext, pData, pCompressedData, pnCompressedDataSize, m_nXRes, m_nYRes, m_nQuality); + } + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + return XnStreamUncompressImageJ(&m_UncompJPEGContext, pCompressedData, nCompressedDataSize, pData, pnDataSize); + } + +private: + const XnBool m_bRGB; + const XnUInt32 m_nXRes; + const XnUInt32 m_nYRes; + const XnUInt32 m_nQuality; + XnStreamCompJPEGContext m_CompJPEGContext; + XnStreamUncompJPEGContext m_UncompJPEGContext; +}; + +#endif //__XN_JPEG_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Formats/XnNiCodec.h b/Source/Drivers/PS1080/Formats/XnNiCodec.h new file mode 100644 index 0000000..68e6510 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnNiCodec.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_XN_CODEC_H__ +#define __XN_XN_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodec.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +//class XN_FORMATS_CPP_API XnNiCodec : public XnCodec +//{ +//public: +// XnNiCodec(xn::Codec& codec) : m_codec(codec) {} +// virtual ~XnNiCodec() {} +// +// virtual XnCompressionFormats GetCompressionFormat() const { return XnCodec::GetCompressionFormatFromCodecID(m_codec.GetCodecID()); } +// +// virtual XnStatus Compress(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) +// { +// return m_codec.EncodeData(pData, nDataSize, pCompressedData, *pnCompressedDataSize, pnCompressedDataSize); +// } +// +// virtual XnStatus Decompress(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) +// { +// return m_codec.DecodeData(pCompressedData, nCompressedDataSize, pData, *pnDataSize, pnDataSize); +// } +// +//private: +// xn::Codec m_codec; +//}; +// + +#endif // __XN_XN_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Formats/XnStreamCompression.cpp b/Source/Drivers/PS1080/Formats/XnStreamCompression.cpp new file mode 100644 index 0000000..129c321 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnStreamCompression.cpp @@ -0,0 +1,1280 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnStreamCompression.h" +#include +extern "C" { +#include +} +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnStreamCompressDepth16Z(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt16* pInputEnd = pInput + (nInputSize / sizeof(XnUInt16)); + XnUInt8* pOrigOutput = pOutput; + XnUInt16 nCurrValue = 0; + XnUInt16 nLastValue = 0; + XnUInt16 nAbsDiffValue = 0; + XnInt16 nDiffValue = 0; + XnUInt8 cOutStage = 0; + XnUInt8 cOutChar = 0; + XnUInt8 cZeroCounter = 0; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize == 0) + { + *pnOutputSize = 0; + return XN_STATUS_OK; + } + + // Encode the data... + nLastValue = *pInput; + *(XnUInt16*)pOutput = nLastValue; + pInput++; + pOutput+=2; + + while (pInput != pInputEnd) + { + nCurrValue = *pInput; + + nDiffValue = (nLastValue - nCurrValue); + nAbsDiffValue = (XnUInt16)abs(nDiffValue); + + if (nAbsDiffValue <= 6) + { + nDiffValue += 6; + + if (cOutStage == 0) + { + cOutChar = (XnUInt8)(nDiffValue << 4); + + cOutStage = 1; + } + else + { + cOutChar += (XnUInt8)nDiffValue; + + if (cOutChar == 0x66) + { + cZeroCounter++; + + if (cZeroCounter == 15) + { + *pOutput = 0xEF; + pOutput++; + + cZeroCounter = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + *pOutput = cOutChar; + pOutput++; + } + + cOutStage = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + if (cOutStage == 0) + { + cOutChar = 0xFF; + } + else + { + cOutChar += 0x0F; + cOutStage = 0; + } + + *pOutput = cOutChar; + pOutput++; + + if (nAbsDiffValue <= 63) + { + nDiffValue += 192; + + *pOutput = (XnUInt8)nDiffValue; + pOutput++; + } + else + { + *(XnUInt16*)pOutput = (nCurrValue << 8) + (nCurrValue >> 8); + pOutput+=2; + } + } + + nLastValue = nCurrValue; + pInput++; + } + + if (cOutStage != 0) + { + *pOutput = cOutChar + 0x0D; + pOutput++; + } + + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressDepth16ZWithEmbTable(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nMaxValue) +{ + // Local function variables + const XnUInt16* pInputEnd = pInput + (nInputSize / sizeof(XnUInt16)); + const XnUInt16* pOrigInput = pInput; + const XnUInt8* pOrigOutput = pOutput; + XnUInt16 nCurrValue = 0; + XnUInt16 nLastValue = 0; + XnUInt16 nAbsDiffValue = 0; + XnInt16 nDiffValue = 0; + XnUInt8 cOutStage = 0; + XnUInt8 cOutChar = 0; + XnUInt8 cZeroCounter = 0; + static XnUInt16 nEmbTable[XN_MAX_UINT16]; + XnUInt16 nEmbTableIdx=0; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + // Create the embedded value translation table... + pOutput+=2; + xnOSMemSet(&nEmbTable[0], 0, nMaxValue*sizeof(XnUInt16)); + + while (pInput != pInputEnd) + { + nEmbTable[*pInput] = 1; + pInput++; + } + + for (XnUInt32 i=0; i> 8)); + pOutput+=2; + } + } + + nLastValue = nCurrValue; + pInput++; + } + + if (cOutStage != 0) + { + *pOutput = cOutChar + 0x0D; + pOutput++; + } + + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressDepth16Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + XnUInt16* pOutputEnd = 0; + const XnUInt16* pOrigOutput = pOutput; + XnUInt16 nLastFullValue = 0; + XnUInt8 cInput = 0; + XnUInt8 cZeroCounter = 0; + XnInt8 cInData1 = 0; + XnInt8 cInData2 = 0; + XnUInt8 cInData3 = 0; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt16)) + { + return (XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL); + } + + pOutputEnd = pOutput + (*pnOutputSize / sizeof(XnUInt16)); + + // Decode the data... + nLastFullValue = *(XnUInt16*)pInput; + *pOutput = nLastFullValue; + pInput+=2; + pOutput++; + + while (pInput != pInputEnd) + { + cInput = *pInput; + + if (cInput < 0xE0) + { + cInData1 = cInput >> 4; + cInData2 = (cInput & 0x0f); + + nLastFullValue -= (cInData1 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + pOutput++; + + if (cInData2 != 0x0f) + { + if (cInData2 != 0x0d) + { + nLastFullValue -= (cInData2 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + pOutput++; + } + + pInput++; + } + else + { + pInput++; + + cInData3 = *pInput; + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pOutput++; + pInput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pOutput++; + pInput++; + } + } + } + else if (cInput == 0xFF) + { + pInput++; + + cInData3 = *pInput; + + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pInput++; + pOutput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = nLastFullValue; + + pInput++; + pOutput++; + } + } + else //It must be 0xE? + { + cZeroCounter = cInput - 0xE0; + + while (cZeroCounter != 0) + { + XN_CHECK_OUTPUT_OVERFLOW(pOutput+1, pOutputEnd); + *pOutput = nLastFullValue; + pOutput++; + + *pOutput = nLastFullValue; + pOutput++; + + cZeroCounter--; + } + + pInput++; + } + } + + *pnOutputSize = (XnUInt32)((pOutput - pOrigOutput) * sizeof(XnUInt16)); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressDepth16ZWithEmbTable(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + XnUInt16* pOutputEnd = 0; + XnUInt16* pOrigOutput = pOutput; + XnUInt16 nLastFullValue = 0; + XnUInt8 cInput = 0; + XnUInt8 cZeroCounter = 0; + XnInt8 cInData1 = 0; + XnInt8 cInData2 = 0; + XnUInt8 cInData3 = 0; + XnUInt16* pEmbTable = NULL; + XnUInt16 nEmbTableIdx = 0; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt16)) + { + return (XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL); + } + + nEmbTableIdx = XN_PREPARE_VAR16_IN_BUFFER(*(XnUInt16*)pInput); + pInput+=2; + pEmbTable = (XnUInt16*)pInput; + pInput+=nEmbTableIdx * 2; + for (XnUInt32 i = 0; i < nEmbTableIdx; i++) + pEmbTable[i] = XN_PREPARE_VAR16_IN_BUFFER(pEmbTable[i]); + + pOutputEnd = pOutput + (*pnOutputSize / sizeof(XnUInt16)); + + // Decode the data... + nLastFullValue = XN_PREPARE_VAR16_IN_BUFFER(*(XnUInt16*)pInput); + *pOutput = pEmbTable[nLastFullValue]; + pInput+=2; + pOutput++; + + while (pInput != pInputEnd) + { + cInput = *pInput; + + if (cInput < 0xE0) + { + cInData1 = cInput >> 4; + cInData2 = (cInput & 0x0f); + + nLastFullValue -= (cInData1 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + + if (cInData2 != 0x0f) + { + if (cInData2 != 0x0d) + { + nLastFullValue -= (cInData2 - 6); + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + } + + pInput++; + } + else + { + pInput++; + + cInData3 = *pInput; + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pOutput++; + pInput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pOutput++; + pInput++; + } + } + } + else if (cInput == 0xFF) + { + pInput++; + + cInData3 = *pInput; + + if (cInData3 & 0x80) + { + nLastFullValue -= (cInData3 - 192); + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pInput++; + pOutput++; + } + else + { + nLastFullValue = cInData3 << 8; + pInput++; + nLastFullValue += *pInput; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + + pInput++; + pOutput++; + } + } + else //It must be 0xE? + { + cZeroCounter = cInput - 0xE0; + + while (cZeroCounter != 0) + { + XN_CHECK_OUTPUT_OVERFLOW(pOutput+1, pOutputEnd); + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + + *pOutput = pEmbTable[nLastFullValue]; + pOutput++; + + cZeroCounter--; + } + + pInput++; + } + } + + *pnOutputSize = (XnUInt32)((pOutput - pOrigOutput) * sizeof(XnUInt16)); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOrigOutput = pOutput; + XnUInt8 nCurrValue = 0; + XnUInt8 nLastValue = 0; + XnUInt8 nAbsDiffValue = 0; + XnInt8 nDiffValue = 0; + XnUInt8 cOutStage = 0; + XnUInt8 cOutChar = 0; + XnUInt8 cZeroCounter = 0; + XnBool bFlag = FALSE; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + // Encode the data... + nLastValue = *pInput; + *pOutput = nLastValue; + pInput++; + pOutput++; + + while (pInput != pInputEnd) + { + nCurrValue = *pInput; + + nDiffValue = (nLastValue - nCurrValue); + nAbsDiffValue = (XnUInt8)abs(nDiffValue); + + if (nAbsDiffValue <= 6) + { + nDiffValue += 6; + + if (cOutStage == 0) + { + cOutChar = nDiffValue << 4; + + cOutStage = 1; + } + else + { + cOutChar += nDiffValue; + + if ((cOutChar == 0x66) && (bFlag == FALSE)) + { + cZeroCounter++; + + if (cZeroCounter == 15) + { + *pOutput = 0xEF; + pOutput++; + + cZeroCounter = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + *pOutput = cOutChar; + pOutput++; + + bFlag = FALSE; + } + + cOutStage = 0; + } + } + else + { + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + + cZeroCounter = 0; + } + + if (cOutStage == 0) + { + cOutChar = 0xF0; + cOutChar += nCurrValue >> 4; + + *pOutput = cOutChar; + pOutput++; + + cOutChar = (nCurrValue & 0xF) << 4; + cOutStage = 1; + + bFlag = TRUE; + } + else + { + cOutChar += 0x0F; + cOutStage = 0; + + *pOutput = cOutChar; + pOutput++; + + *pOutput = nCurrValue; + pOutput++; + } + } + + nLastValue = nCurrValue; + pInput++; + } + + if (cOutStage != 0) + { + *pOutput = cOutChar + 0x0D; + pOutput++; + } + + if (cZeroCounter != 0) + { + *pOutput = 0xE0 + cZeroCounter; + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOrigOutput = pOutput; + XnUInt8 nLastFullValue = 0; + XnUInt8 cInput = 0; + XnUInt8 cZeroCounter = 0; + XnInt8 cInData1 = 0; + XnInt8 cInData2 = 0; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt8)) + { + return (XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL); + } + + // Decode the data... + nLastFullValue = *pInput; + *pOutput = nLastFullValue; + pInput++; + pOutput++; + + while (pInput != pInputEnd) + { + cInput = *pInput; + + if (cInput < 0xE0) + { + cInData1 = cInput >> 4; + cInData2 = (cInput & 0x0f); + + nLastFullValue -= (cInData1 - 6); + *pOutput = nLastFullValue; + pOutput++; + + if (cInData2 != 0x0f) + { + if (cInData2 != 0x0d) + { + nLastFullValue -= (cInData2 - 6); + *pOutput = nLastFullValue; + pOutput++; + } + } + else + { + pInput++; + nLastFullValue = *pInput; + *pOutput = nLastFullValue; + pOutput++; + } + + pInput++; + } + else if (cInput >= 0xF0) + { + cInData1 = cInput << 4; + + pInput++; + cInput = *pInput; + + nLastFullValue = cInData1 + (cInput >> 4); + + *pOutput = nLastFullValue; + pOutput++; + + cInData2 = cInput & 0xF; + + if (cInData2 == 0x0F) + { + pInput++; + nLastFullValue = *pInput; + *pOutput = nLastFullValue; + pOutput++; + pInput++; + } + else + { + if (cInData2 != 0x0D) + { + nLastFullValue -= (cInData2 - 6); + *pOutput = nLastFullValue; + pOutput++; + } + + pInput++; + } + } + else //It must be 0xE? + { + cZeroCounter = cInput - 0xE0; + + while (cZeroCounter != 0) + { + *pOutput = nLastFullValue; + pOutput++; + + *pOutput = nLastFullValue; + pOutput++; + + cZeroCounter--; + } + + pInput++; + } + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOrigOutput = pOutput; + + // Note: this function does not make sure it stay within the output memory boundaries! + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + // Encode the data... + while (pInput != pInputEnd) + { + *pOutput = *pInput << 4; + pInput++; + + *pOutput += *pInput; + pInput++; + + pOutput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + const XnUInt8* pInputEnd = pInput + nInputSize; + const XnUInt8* pOutputEnd = 0; + const XnUInt8* pOrigOutput = pOutput; + XnUInt8 nValue1; + XnUInt8 nValue2; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + XN_VALIDATE_INPUT_PTR(pnOutputSize); + + if (nInputSize < sizeof(XnUInt8)) + { + return (XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL); + } + + if (nInputSize % 2 != 0) + { + return (XN_STATUS_IO_INVALID_COMPRESSED_BUFFER_SIZE); + } + + pOutputEnd = pOutput + *pnOutputSize; + + XN_CHECK_OUTPUT_OVERFLOW(pOutput + (nInputSize * 2), pOutputEnd); + + while (pInput != pInputEnd) + { + nValue1 = pInput[0]; + nValue2 = pInput[1]; + + pOutput[0] = nValue1 >> 4; + pOutput[1] = nValue1 & 0xF; + pOutput[2] = nValue2 >> 4; + pOutput[3] = nValue2 & 0xF; + + pOutput+=4; + pInput+=2; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput); + + // All is good... + return (XN_STATUS_OK); +} + +void XnStreamJPEGCompDummyFunction(struct jpeg_compress_struct* /*pjCompStruct*/) +{ + // Dummy libjpeg function to wrap internal buffers usage... +} + +boolean XnStreamJPEGCompDummyFailFunction(struct jpeg_compress_struct* /*pjCompStruct*/) +{ + // If we ever got to the point we need to allocate more memory, something is wrong! + return (FALSE); +} + +XnStatus XnStreamInitCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_OUTPUT_PTR(pStreamCompJPEGContext); + + pStreamCompJPEGContext->jCompStruct.err = jpeg_std_error(&pStreamCompJPEGContext->jErrMgr); + + jpeg_create_compress(&pStreamCompJPEGContext->jCompStruct); + + pStreamCompJPEGContext->jCompStruct.dest = &pStreamCompJPEGContext->jDestMgr; + pStreamCompJPEGContext->jCompStruct.dest->init_destination = XnStreamJPEGCompDummyFunction; + pStreamCompJPEGContext->jCompStruct.dest->empty_output_buffer = XnStreamJPEGCompDummyFailFunction; + pStreamCompJPEGContext->jCompStruct.dest->term_destination = XnStreamJPEGCompDummyFunction; + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamFreeCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamCompJPEGContext); + + jpeg_destroy_compress(&pStreamCompJPEGContext->jCompStruct); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressImage8J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality) +{ + // Local function variables + XnUInt8* pCurrScanline = (XnUInt8*)pInput; + XnUInt32 nYIndex = 0; + jpeg_compress_struct* pjCompStruct = NULL; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamCompJPEGContext); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_OUTPUT_PTR(pOutput); + XN_VALIDATE_OUTPUT_PTR(pnOutputSize); + + pjCompStruct = &pStreamCompJPEGContext->jCompStruct; + + pjCompStruct->in_color_space = JCS_GRAYSCALE; + jpeg_set_defaults(pjCompStruct); + pjCompStruct->input_components = 1; + pjCompStruct->num_components = 1; + pjCompStruct->image_width = nXRes; + pjCompStruct->image_height = nYRes; + pjCompStruct->data_precision = 8; + pjCompStruct->input_gamma = 1.0; + + jpeg_set_quality(pjCompStruct, nQuality, FALSE); + + pjCompStruct->dest->next_output_byte = (JOCTET*)pOutput; + pjCompStruct->dest->free_in_buffer = *pnOutputSize; + + jpeg_start_compress(pjCompStruct, TRUE); + + for (nYIndex = 0; nYIndex < nYRes; nYIndex++) + { + jpeg_write_scanlines(pjCompStruct, &pCurrScanline, 1); + + pCurrScanline += nXRes; + } + + jpeg_finish_compress(pjCompStruct); + + *pnOutputSize -= (XnUInt32)pjCompStruct->dest->free_in_buffer; + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamCompressImage24J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality) +{ + // Local function variables + XnUInt8* pCurrScanline = (XnUChar*)pInput; + XnUInt32 nYIndex = 0; + XnUInt32 nScanLineSize = 0; + jpeg_compress_struct* pjCompStruct = NULL; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamCompJPEGContext); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_OUTPUT_PTR(pOutput); + XN_VALIDATE_OUTPUT_PTR(pnOutputSize); + + pjCompStruct = &pStreamCompJPEGContext->jCompStruct; + + pjCompStruct->in_color_space = JCS_RGB; + jpeg_set_defaults(pjCompStruct); + pjCompStruct->input_components = 3; + pjCompStruct->num_components = 3; + pjCompStruct->image_width = nXRes; + pjCompStruct->image_height = nYRes; + pjCompStruct->data_precision = 8; + pjCompStruct->input_gamma = 1.0; + + jpeg_set_quality(pjCompStruct, nQuality, FALSE); + + pjCompStruct->dest->next_output_byte = (JOCTET*)pOutput; + pjCompStruct->dest->free_in_buffer = *pnOutputSize; + + jpeg_start_compress(pjCompStruct, TRUE); + + nScanLineSize = nXRes * 3; + for (nYIndex = 0; nYIndex < nYRes; nYIndex++) + { + jpeg_write_scanlines(pjCompStruct, &pCurrScanline, 1); + + pCurrScanline += nScanLineSize; + } + + jpeg_finish_compress(pjCompStruct); + + *pnOutputSize -= (XnUInt32)pjCompStruct->dest->free_in_buffer; + + // All is good... + return (XN_STATUS_OK); +} + +void XnStreamJPEGDecompDummyFunction(struct jpeg_decompress_struct* /*pjDecompStruct*/) +{ + // Dummy libjpeg function to wrap internal buffers usage... +} + +boolean XnStreamJPEGDecompDummyFailFunction(struct jpeg_decompress_struct* /*pjDecompStruct*/) +{ + // If we ever got to the point we need to allocate more memory, something is wrong! + return (FALSE); +} + +void XnStreamJPEGDecompSkipFunction(struct jpeg_decompress_struct* pjDecompStruct, long nNumBytes) +{ + // Skip bytes in the internal buffer + pjDecompStruct->src->next_input_byte += (size_t)nNumBytes; + pjDecompStruct->src->bytes_in_buffer -= (size_t)nNumBytes; +} + +void XnStreamJPEGDummyErrorExit(j_common_ptr cinfo) +{ + XnLibJpegErrorMgr* errMgr = (XnLibJpegErrorMgr*)cinfo->err; + + longjmp(errMgr->setjmpBuffer, 1); +} + +void XnStreamJPEGOutputMessage(j_common_ptr cinfo) +{ + struct jpeg_error_mgr* err = cinfo->err; + int msg_code = err->msg_code; + if (msg_code == JWRN_EXTRANEOUS_DATA) + { + // NOTE: we are aware this problem occurs. Log a warning every once in a while + static XnUInt32 nTimes = 0; + if (++nTimes == 50) + { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + //Temporary disabled this error since it happens all the time and it's a known issue. + //xnLogWarning(XN_MASK_JPEG, "JPEG: The following warning occurred 50 times: %s", buffer); + nTimes = 0; + } + } + else + { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + xnLogWarning(XN_MASK_JPEG, "JPEG: %s", buffer); + } +} + +XnStatus XnStreamInitUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_OUTPUT_PTR(pStreamUncompJPEGContext); + + pStreamUncompJPEGContext->jDecompStruct.err = jpeg_std_error(&pStreamUncompJPEGContext->jErrMgr.pub); + pStreamUncompJPEGContext->jErrMgr.pub.output_message = XnStreamJPEGOutputMessage; + pStreamUncompJPEGContext->jErrMgr.pub.error_exit = XnStreamJPEGDummyErrorExit; + + jpeg_create_decompress(&pStreamUncompJPEGContext->jDecompStruct); + + pStreamUncompJPEGContext->jDecompStruct.src = &pStreamUncompJPEGContext->jSrcMgr; + pStreamUncompJPEGContext->jDecompStruct.src->init_source = XnStreamJPEGDecompDummyFunction; + pStreamUncompJPEGContext->jDecompStruct.src->fill_input_buffer = XnStreamJPEGDecompDummyFailFunction; + pStreamUncompJPEGContext->jDecompStruct.src->skip_input_data = XnStreamJPEGDecompSkipFunction; + pStreamUncompJPEGContext->jDecompStruct.src->resync_to_restart = jpeg_resync_to_restart; + pStreamUncompJPEGContext->jDecompStruct.src->term_source = XnStreamJPEGDecompDummyFunction; + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamFreeUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext) +{ + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamUncompJPEGContext); + + jpeg_destroy_decompress(&pStreamUncompJPEGContext->jDecompStruct); + + // All is good... + return (XN_STATUS_OK); +} + +// to allow the use of setjmp +#if (ONI_PLATFORM == ONI_PLATFORM_WIN32) +#pragma warning(push) +#pragma warning(disable: 4611) +#endif + +XnStatus XnStreamUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext, const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + // Local function variables + XnUInt8* pCurrScanline = pOutput; + XnUInt8* pNextScanline = NULL; + XnUInt8* pOutputEnd = 0; + XnUInt32 nScanLineSize = 0; + XnUInt32 nOutputSize = 0; + jpeg_decompress_struct* pjDecompStruct = NULL; + + // Validate the input/output pointers (to make sure none of them is NULL) + XN_VALIDATE_INPUT_PTR(pStreamUncompJPEGContext); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_OUTPUT_PTR(pOutput); + XN_VALIDATE_OUTPUT_PTR(pnOutputSize); + + if (nInputSize == 0) + { + return (XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL); + } + + pOutputEnd = pOutput + *pnOutputSize; + + pjDecompStruct = &pStreamUncompJPEGContext->jDecompStruct; + + pjDecompStruct->src->bytes_in_buffer = nInputSize; + pjDecompStruct->src->next_input_byte = pInput; + + if (setjmp(pStreamUncompJPEGContext->jErrMgr.setjmpBuffer)) + { + //If we get here, the JPEG code has signaled an error. + XnStreamFreeUncompressImageJ(pStreamUncompJPEGContext); + XnStreamInitUncompressImageJ(pStreamUncompJPEGContext); + + *pnOutputSize = 0; + + return (XN_STATUS_IO_DECOMPRESSION_FAILED); + } + + jpeg_read_header(pjDecompStruct, TRUE); + + jpeg_start_decompress(pjDecompStruct); + + nScanLineSize = pjDecompStruct->output_width * pjDecompStruct->num_components; + + nOutputSize = pjDecompStruct->output_height * nScanLineSize; + if (nOutputSize > *pnOutputSize) + { + XnStreamFreeUncompressImageJ(pStreamUncompJPEGContext); + XnStreamInitUncompressImageJ(pStreamUncompJPEGContext); + + *pnOutputSize = 0; + + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + while (pStreamUncompJPEGContext->jDecompStruct.output_scanline < pStreamUncompJPEGContext->jDecompStruct.output_height) + { + pNextScanline = pCurrScanline+nScanLineSize; + + if (pNextScanline > pOutputEnd) + { + XnStreamFreeUncompressImageJ(pStreamUncompJPEGContext); + XnStreamInitUncompressImageJ(pStreamUncompJPEGContext); + + *pnOutputSize = 0; + + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + jpeg_read_scanlines(pjDecompStruct, &pCurrScanline, 1); + pCurrScanline = pNextScanline; + } + + jpeg_finish_decompress(pjDecompStruct); + + *pnOutputSize = nOutputSize; + + // All is good... + return (XN_STATUS_OK); +} + +#if (ONI_PLATFORM == ONI_PLATFORM_WIN32) +#pragma warning(pop) +#endif diff --git a/Source/Drivers/PS1080/Formats/XnStreamCompression.h b/Source/Drivers/PS1080/Formats/XnStreamCompression.h new file mode 100644 index 0000000..9a762c0 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnStreamCompression.h @@ -0,0 +1,104 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_STREAMCOMPRESSION_H_ +#define _XN_STREAMCOMPRESSION_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFormats.h" +#include +extern "C" { +#include +} +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_STREAM_COMPRESSION_DEPTH16Z_WORSE_RATIO 1.333F +#define XN_STREAM_COMPRESSION_IMAGE8Z_WORSE_RATIO 1.333F +#define XN_STREAM_COMPRESSION_IMAGEJ_WORSE_RATIO 1.2F +#define XN_STREAM_COMPRESSION_CONF4_WORSE_RATIO 0.51F +#define XN_STREAM_COMPRESSION_JPEG_DEFAULT_QUALITY 90 + +#define XN_STREAM_STRING_BAD_FORMAT -1 + +#define XN_MASK_JPEG "JPEG" + +//--------------------------------------------------------------------------- +// Structs +//--------------------------------------------------------------------------- + +XN_PRAGMA_START_DISABLED_WARNING_SECTION(XN_STRUCT_PADDED_WARNING_ID); + +typedef struct XnLibJpegErrorMgr +{ + struct jpeg_error_mgr pub; + + jmp_buf setjmpBuffer; +} XnLibJpegErrorMgr; + +XN_PRAGMA_STOP_DISABLED_WARNING_SECTION; + +typedef struct XnStreamCompJPEGContext +{ + jpeg_compress_struct jCompStruct; + jpeg_error_mgr jErrMgr; + struct jpeg_destination_mgr jDestMgr; +} XnStreamCompJPEGContext; + +typedef struct XnStreamUncompJPEGContext +{ + jpeg_decompress_struct jDecompStruct; + XnLibJpegErrorMgr jErrMgr; + struct jpeg_source_mgr jSrcMgr; +} XnStreamUncompJPEGContext; + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +XnStatus XnStreamCompressDepth16Z(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamCompressDepth16ZWithEmbTable(const XnUInt16* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nMaxValue); +XnStatus XnStreamUncompressDepth16Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamUncompressDepth16ZWithEmbTable(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt16* pOutput, XnUInt32* pnOutputSize); + +XnStatus XnStreamCompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamUncompressImage8Z(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); + +XnStatus XnStreamCompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); +XnStatus XnStreamUncompressConf4(const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); + +void XnStreamJPEGCompDummyFunction(struct jpeg_compress_struct* pjCompStruct); +boolean XnStreamJPEGCompDummyFailFunction(struct jpeg_compress_struct* pjCompStruct); +XnStatus XnStreamInitCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext); +XnStatus XnStreamFreeCompressImageJ(XnStreamCompJPEGContext* pStreamCompJPEGContext); +XnStatus XnStreamCompressImage8J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality); +XnStatus XnStreamCompressImage24J(XnStreamCompJPEGContext* pStreamCompJPEGContext, const XnUInt8* pInput, XnUInt8* pOutput, XnUInt32* pnOutputSize, const XnUInt32 nXRes, const XnUInt32 nYRes, const XnUInt32 nQuality); + +void XnStreamJPEGDecompDummyFunction(struct jpeg_decompress_struct* pjDecompStruct); +boolean XnStreamJPEGDecompDummyFailFunction(struct jpeg_decompress_struct* pjDecompStruct); +void XnStreamJPEGDecompSkipFunction(struct jpeg_decompress_struct* pjDecompStruct, XnInt nNumBytes); +XnStatus XnStreamInitUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext); +XnStatus XnStreamFreeUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext); +XnStatus XnStreamUncompressImageJ(XnStreamUncompJPEGContext* pStreamUncompJPEGContext, const XnUInt8* pInput, const XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize); + +#endif //_XN_STREAMCOMPRESSION_H_ diff --git a/Source/Drivers/PS1080/Formats/XnUncompressedCodec.h b/Source/Drivers/PS1080/Formats/XnUncompressedCodec.h new file mode 100644 index 0000000..bd98516 --- /dev/null +++ b/Source/Drivers/PS1080/Formats/XnUncompressedCodec.h @@ -0,0 +1,68 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_UNCOMPRESSED_CODEC_H__ +#define __XN_UNCOMPRESSED_CODEC_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCodecBase.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnUncompressedCodec : public XnCodecBase +{ +public: + XnUncompressedCodec() {} + virtual ~XnUncompressedCodec() {} + + virtual XnCompressionFormats GetCompressionFormat() const { return XN_COMPRESSION_NONE; } + virtual XnFloat GetWorseCompressionRatio() const { return 1.0; } + virtual XnUInt32 GetOverheadSize() const { return 0; } + +protected: + virtual XnStatus CompressImpl(const XnUChar* pData, XnUInt32 nDataSize, XnUChar* pCompressedData, XnUInt32* pnCompressedDataSize) + { + if (nDataSize > *pnCompressedDataSize) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + xnOSMemCopy(pCompressedData, pData, nDataSize); + *pnCompressedDataSize = nDataSize; + return (XN_STATUS_OK); + } + + virtual XnStatus DecompressImpl(const XnUChar* pCompressedData, XnUInt32 nCompressedDataSize, XnUChar* pData, XnUInt32* pnDataSize) + { + if (nCompressedDataSize > *pnDataSize) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + xnOSMemCopy(pData, pCompressedData, nCompressedDataSize); + *pnDataSize = nCompressedDataSize; + return (XN_STATUS_OK); + } +}; + +#endif //__XN_UNCOMPRESSED_CODEC_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Include/XnCommon.h b/Source/Drivers/PS1080/Include/XnCommon.h new file mode 100644 index 0000000..86ffbd9 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnCommon.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_COMMON_H__ +#define __XN_COMMON_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_VENDOR_PRIMESENSE "PrimeSense" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef enum XnPrimeSenseErrorModules +{ + XN_ERROR_GROUP_SECURITY = 0, + XN_ERROR_GROUP_FORMATS = 1000, + XN_ERROR_GROUP_DDK = 2000, + XN_ERROR_GROUP_DEVICE = 3000, + XN_ERROR_GROUP_IO = 4000, + XN_ERROR_GROUP_EE_CORE = 5000, + XN_ERROR_GROUP_EE_FRAMEWORK = 6000, + XN_ERROR_GROUP_EE_NITE = 7000, +} XnPrimeSenseErrorModules; + +#define XN_PS_STATUS_MESSAGE_MAP_START(module) \ + XN_STATUS_MESSAGE_MAP_START_FROM(XN_ERROR_GROUP_PRIMESENSE, module) + +#define XN_PS_STATUS_MESSAGE_MAP_END(module) \ + XN_STATUS_MESSAGE_MAP_END_FROM(XN_ERROR_GROUP_PRIMESENSE, module) + +#endif // __XN_COMMON_H__ diff --git a/Source/Drivers/PS1080/Include/XnCore.h b/Source/Drivers/PS1080/Include/XnCore.h new file mode 100644 index 0000000..1e56aa5 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnCore.h @@ -0,0 +1,107 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_CORE_H_ +#define _XN_CORE_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- + +/** +* Packs a pointer and a size into an XnGeneralBuffer struct. +XnGeneralBuffer.h-51-*/ +inline OniGeneralBuffer XnGeneralBufferPack(void* pData, XnUInt32 nDataSize) +{ + OniGeneralBuffer result; + result.data = pData; + result.dataSize = nDataSize; + return result; +} +#define XN_PACK_GENERAL_BUFFER(x) XnGeneralBufferPack(&x, sizeof(x)) + +/** +* Copies one general buffer into another. +*/ +XnStatus XnGeneralBufferCopy(OniGeneralBuffer* pDest, const OniGeneralBuffer* pSrc); +XnStatus XnGeneralBufferAlloc(OniGeneralBuffer* pDest, XnUInt32 nSize); +XnStatus XnGeneralBufferRealloc(OniGeneralBuffer* pDest, XnUInt32 nSize); +void XnGeneralBufferFree(OniGeneralBuffer* pDest); + +#define XN_VALIDATE_GENERAL_BUFFER_TYPE(gb, t) \ + if ((gb).dataSize != sizeof(t)) \ + { \ + return XN_STATUS_INVALID_BUFFER_SIZE; \ + } + + +/** represents a value for automatic control for nodes supporting it, as part of the @ref general_int. **/ +#define XN_AUTO_CONTROL XN_MIN_INT32 + +//--------------------------------------------------------------------------- +// Exported Function Declaration +//--------------------------------------------------------------------------- +/** + * This function initializes the core low-level SDK. + */ +XnStatus XnInit(); + +/** +* This function initializes the core low-level SDK from an INI file. +* Please refer to the low-level SDK overview/tutorial section for a complete list of INI entries. +* Note: This function is not very useful on its own. You should use the I/O subsystem initializing instead. +* +* @param cpINIFileName [in] A path to an INI file. +*/ +XnStatus XnInitFromINIFile(const XnChar* cpINIFileName); + +/** +* This function shuts down the core low-level SDK. +* Note: This function is not very useful on its own. You should use the I/O subsystem shutdown instead. +*/ +XnStatus XnShutdown(); + +/** + * Returns the Xiron version as an integer calculated from this formula: + * (Xiron major version * 1000 + Xiron minor version) + * + * @return An integer representation of the Xiron version. + */ +XnUInt32 XnGetVersion(void); + +/** + * Returns the Xiron version as a string in the following format: + * "Major.Minor-Platform (MMM DD YYYY HH:MM:SS)" + * For example: "1.0-Win32 (Sep 19 2006 11:22:33)" + * + * @return A string representation of the Xiron version. + */ +const XnChar* XnGetVersionString(void); + +#endif //_XN_CORE_H_ diff --git a/Source/Drivers/PS1080/Include/XnDDK.h b/Source/Drivers/PS1080/Include/XnDDK.h new file mode 100644 index 0000000..1963bbc --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnDDK.h @@ -0,0 +1,53 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_DDK_H_ +#define _XN_DDK_H_ + +#include +#include +#include +#include + +#define XN_MASK_DDK "DDK" + +/** +* This function initializes the DDK library. +* This function must be called BEFORE calling any other method of a device. +*/ +XnStatus XnDDKInit(const XnChar* strDevicesDir); + +/** +* This function initializes the DDK library from an INI file. +* +* @param cpINIFileName [in] The name of the INI file. +*/ +XnStatus XnDDKInitFromINIFile(const XnChar* cpINIFileName); + +/** +* This function shuts down the DDK library. +*/ +XnStatus XnDDKShutdown(); + +XnResolutions XnDDKGetResolutionFromXY(XnUInt32 nXRes, XnUInt32 nYRes); +XnBool XnDDKGetXYFromResolution(XnResolutions res, XnUInt32* pnXRes, XnUInt32* pnYRes); +const XnChar* XnDDKGetResolutionName(XnResolutions res); + +#endif //_XN_DDK_H_ diff --git a/Source/Drivers/PS1080/Include/XnDDKStatus.h b/Source/Drivers/PS1080/Include/XnDDKStatus.h new file mode 100644 index 0000000..7872bd7 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnDDKStatus.h @@ -0,0 +1,162 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_DDK_STATUS_H_ +#define _XN_DDK_STATUS_H_ + +#include +#include +#include + +XN_PS_STATUS_MESSAGE_MAP_START(XN_ERROR_GROUP_DDK) +XN_STATUS_MESSAGE(XN_STATUS_DDK_NOT_INIT, "Xiron DDK library was not initialized!") +XN_STATUS_MESSAGE(XN_STATUS_DDK_ALREADY_INIT, "Xiron DDK library was already initialized!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_DEPTH_RESOLUTION, "Invalid Xiron I/O stream depth resolution!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_IMAGE_RESOLUTION, "Invalid Xiron I/O stream image resolution!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_FPS, "Invalid Xiron I/O stream frame per second!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_ZP_DISTANCE, "Invalid Xiron I/O stream zero plane distance!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_ZP_PIXEL_SIZE, "Invalid Xiron I/O stream zero pixel size!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_SAMPLE_RATE, "Invalid Xiron I/O stream audio sample rate!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_NUMBER_OF_CHANNELS, "Invalid number of audio channels!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEPTH_BUFFER_TOO_SMALL, "The stream frame depth buffer is too small to contain the requested data!") +XN_STATUS_MESSAGE(XN_STATUS_IO_IMAGE_BUFFER_TOO_SMALL, "The stream frame image buffer is too small to contain the requested data!") +XN_STATUS_MESSAGE(XN_STATUS_IO_MISC_BUFFER_TOO_SMALL, "The stream frame misc buffer is too small to contain the requested data!") +XN_STATUS_MESSAGE(XN_STATUS_IO_AUDIO_BUFFER_TOO_SMALL, "The stream frame audio buffer is too small to contain the requested data!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_MODE, "Invalid Xiron I/O device mode!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_MODE_NOT_SUPPORTED, "The requested Xiron I/O device mode is not supported!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_FUNCTION, "Invalid Xiron I/O device function!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_FUNCTION_NOT_SUPPORTED, "This function is not supported by this Xiron I/O device.") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_READ_MODE, "Invalid Xiron I/O stream audio read mode!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_CHUNK_SIZE, "Invalid Xiron I/O stream audio read chunk size!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_WRONG_VERSION, "This device version is not supported!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_WRONG_SERIAL, "The device serial number is incorrect!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_CONNECTION_STRING, "The connection string is invalid!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_NOT_RESPONDING, "The device is not responding!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_BAD_PARAM_NAME, "The device parameter name is invalid!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_FRAME_HEADER, "Invalid Xiron I/O stream frame header!") +XN_STATUS_MESSAGE(XN_STATUS_IO_STREAM_NOT_SEQUENTIAL, "This stream is not sequential!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_SERVER_CONNECT_FAILED, "Failed to connect to the server!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_RESPONSE_MAGIC, "Got and invalid response magic from the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_RESPONSE_TYPE, "Got and invalid response type from the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_RESPONSE_SIZE, "Got and invalid response size from the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_RESPONSE_ORDER, "Got and invalid response order from the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_MAGIC, "Invalid Xiron I/O stream magic!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_OPEN_BY_ANOTHER_APPLICATION, "A device is already used by another application!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_BAD_PARAM, "Bad Parameter sent to the device!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_USB_DISCONNECTED, "USB is disconnected!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_USB_ERROR, "USB operation error!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_BAD_MAGIC, "Device Protocol: Bad Magic Received!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_WRONG_OPCODE, "Device Protocol: Unexpected opcode!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_UNSUPPORTED_OPCODE, "Device Protocol: Unsupported opcode!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_WRONG_ID, "Device Protocol: Unexpected ID!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_UNKNOWN_ERROR, "Device Protocol: Unknown Error!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_INVALID_COMMAND, "Device Protocol: Command Invalid!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_BAD_PACKET_CRC, "Device Protocol: CRC Error!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_BAD_PACKET_SIZE, "Device Protocol: Wrong packet size!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_BAD_PARAMS, "Device Protocol: Bad Parameter sent!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_BAD_COMMAND_SIZE, "Device Protocol: Bad command size!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_NOT_READY, "Device Protocol: Device is not ready!") +XN_STATUS_MESSAGE(XN_STATUS_WRONG_AUDIO_READ_MODE, "Device Protocol: Audio read mode is wrong!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_UNSUPPORTED_MODE, "Unsupported Mode!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_UNSUPPORTED_PARAMETER, "Unsupported Parameter!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_NOT_ENOUGH_INFORMATION, "Not enough information!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_INVALID_MAX_SHIFT, "Max shift value is too big!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_INVALID_MAX_DEPTH, "Max depth value is too big!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_FRAMES_NOT_SYNCHED, "Didn't get any synched frame!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROJECTOR_FAULT, "A projector fault is in progress!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_SAFE_MODE, "Device is in safe mode. Cannot start any stream!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_OVERHEAT, "The device has overheat!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROPERTY_ALREADY_EXISTS, "Property already exists!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROPERTY_DONT_EXIST, "No such property!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROPERTY_BAD_TYPE, "The property is of the wrong type!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROPERTY_READ_ONLY, "The property is read only and cannot be set!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROPERTY_WRITE_ONLY, "The property is write only and cannot be get!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROPERTY_OUT_OF_RANGE, "Value is out of range and cannot be set!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROPERTY_SIZE_DONT_MATCH, "General buffer passed to property has the wrong size!") +XN_STATUS_MESSAGE(XN_STATUS_STREAM_OUTPUT_BUFFER_TOO_SMALL, "The stream output buffer is too small to contain the requested data!") +XN_STATUS_MESSAGE(XN_STATUS_STREAM_OUTPUT_SET_ALREADY_IN_SET, "The set already contains an object of that stream!") +XN_STATUS_MESSAGE(XN_STATUS_MODULE_IS_NOT_STREAM, "This module is not a stream!") +XN_STATUS_MESSAGE(XN_STATUS_UNSUPPORTED_STREAM, "This stream is not supported by the device!") +XN_STATUS_MESSAGE(XN_STATUS_STREAM_ALREADY_EXISTS, "This stream already exists!") +XN_STATUS_MESSAGE(XN_STATUS_STREAM_NOT_OPEN, "This stream is not open!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_STREAM_IS_ON, "This change can only be made while stream is Off!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_FILE_CORRUPTED, "The file is corrupted!") +XN_STATUS_MESSAGE(XN_STATUS_IO_NO_DEVICES, "No Xiron I/O devices found!") +XN_STATUS_MESSAGE(XN_STATUS_IO_FAILED_FREE_DEVICES, "Failed to free all Xiron I/O devices!") +XN_STATUS_MESSAGE(XN_STATUS_IO_FAILED_CLOSE_DEVICES, "Failed to close all Xiron I/O devices!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_NOT_FOUND, "Xiron I/O device not found!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_NOT_LOADED, "Xiron I/O device is not loaded!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_NOT_OPENED, "Xiron I/O device is not opened!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_DESCRIPTION_FAILED, "Xiron I/O failed to get a valid device description!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_ALREADY_EXISTS, "Xiron I/O device already exists!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_ILLEGAL_NAME, "Xiron I/O device name is illegal!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_VERSION_MISMATCH, "Xiron I/O device version mismatch!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_STRING_TOO_LONG, "Xiron I/O device string is too long!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INIT_FAILED, "Device failed to initialize properly!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_READ_FAILED, "Device read error!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_SET_CLOCK_FAILED, "Failed to set the device clock speed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_GET_CLOCK_FAILED, "Failed to get the device clock speed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_SET_TIMEOUT_FAILED, "Failed to set the device timeout!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_WRITE_FAILED, "Device write error!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_WRONG_STATE, "The device is in the wrong operation state to perform this function!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_WRONG_MODE, "The device is in the wrong mode to perform this function!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_DRIVER_ERROR, "The device low-level driver returned an error!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_WRONG_HARDWARE, "The device is incompatible with this hardware!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_OPEN_FAILED, "Failed to open the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_HARDWARE_OPEN_FAILED, "Failed to open the device hardware!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_GET_TYPE_FAILED, "Failed to get the device hardware type!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_CLOSE_FAILED, "Failed to close the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_IOCONTROL_FAILED, "Device I/O control failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_IOOVERLAP_FAILED, "Device overlapped I/O failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_MISSING_INIT_FILE, "The device initialization file is missing!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_INIT_FILE, "The device initialization file is invalid!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_DEPTH_BUFFER, "Got and invalid depth buffer from the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_IMAGE_BUFFER, "Got and invalid image buffer from the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_MISC_BUFFER, "Got and invalid misc buffer from the device!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_FAILED_SANITY_CHECK, "The device failed the startup sanity check!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_MISSING_FIRMWARE, "The device firmware file is missing!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_CLEAR_FIRMWARE_FAILED, "Failed to clear the device firmware!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_UPDATE_FIRMWARE_FAILED, "Failed to update the device firmware!") +XN_STATUS_MESSAGE(XN_STATUS_IO_NO_FREE_HANDLE, "Xiron I/O couldn't find a free device handle!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_FLAG, "Invalid stream flag!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INIT_FAILED, "Xiron I/O init failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_SEEK_FAILED, "Xiron I/O seek failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_CALLBACK_FAILED, "The callback function failed!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_MODULE_NOT_FOUND, "The module (or stream) does not exist!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_MODULE_ALREADY_EXISTS, "The module (or stream) already exists!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_INVALID_SHARING, "Invalid Xiron I/O device sharing mode!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_SERVER_DISCONNECTED, "The server has disconnected!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_SERVER_ALREADY_RUNNING, "The server is already running!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_OVERFLOW, "Device Protocol: Overflow!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_OVERLAY_NOT_LOADED, "Device Protocol: Overlay not loaded!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_FILE_SYSTEM_LOCKED, "Device Protocol: Filesystem is locked!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_I2C_TRANSACTION_FAILED, "Device Protocol: I2C Transaction failed!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_FILE_NOT_FOUND, "Device Protocol: File not found!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_FILE_CREATE_FAILURE, "Device Protocol: Couldn't create file!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_FILE_WRITE_FAILURE, "Device Protocol: Couldn't write file!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_FILE_DELETE_FAILURE, "Device Protocol: Couldn't delete file!") +XN_STATUS_MESSAGE(XN_STATUS_DEVICE_PROTOCOL_FILE_READ_FAILURE, "Device Protocol: Couldn't read file!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_ROM_MISSING, "The device ROM is missing!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_ROM_FILE_MISSING, "The device ROM file is missing!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_ROM_IO_ERROR, "The device ROM I/O command failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DEVICE_ROM_ERASE_ERROR, "Failed to delete a file from the device ROM!") +XN_PS_STATUS_MESSAGE_MAP_END(XN_ERROR_GROUP_DDK) + +#endif //_XN_DDK_STATUS_H_ diff --git a/Source/Drivers/PS1080/Include/XnDevice.h b/Source/Drivers/PS1080/Include/XnDevice.h new file mode 100644 index 0000000..83babfc --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnDevice.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_DEVICE_H_ +#define _XN_DEVICE_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include + +typedef XnChar XnConnectionString[XN_DEVICE_MAX_STRING_LENGTH]; + +/** This structure defines the Xiron device configuration (when opening a new device). */ +typedef struct XnDeviceConfig +{ + /** The connection string (depending on the device this could mean: file name, IP, sensor serial, etc...). */ + const XnChar* cpConnectionString; + /** Optional. A set of initial values to be used. */ + const XnPropertySet* pInitialValues; +} XnDeviceConfig; + +typedef struct XnNewStreamDataEventArgs +{ + const XnChar* strStreamName; + OniFrame* pFrame; +} XnNewStreamDataEventArgs; + +typedef void (XN_CALLBACK_TYPE* XnDeviceOnPropertyChangedEventHandler)(const XnChar* ModuleName, XnUInt32 nPropertyId, void* pCookie); +typedef void (XN_CALLBACK_TYPE* XnDeviceOnNewStreamDataEventHandler)(const XnNewStreamDataEventArgs& args, void* pCookie); + + +#endif //_XN_DEVICE_H_ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Include/XnDeviceProto.inl b/Source/Drivers/PS1080/Include/XnDeviceProto.inl new file mode 100644 index 0000000..da07d7f --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnDeviceProto.inl @@ -0,0 +1,400 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +/** +* Gets the definition of a device. +* +* @param pDeviceDefinition [out] The returned device definition. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetDefinition, (XnDeviceDefinition* pDeviceDefinition)) + +/** +* This function returns a list of possible connection strings for the specified device. (For example, the list of attached sensors' serial numbers', in the case of a sensor device). +* +* @param aConnectionStrings [in/out] An array to be filled with connection strings. +* @param pnCount [in/out] In: the size of the array. Out: the number of elements filled in the array. +*/ +XN_DEVICE_INTERFACE_FUNCTION(Enumerate, (XnConnectionString* aConnectionStrings, XnUInt32* pnCount)) + +/** +* This function will create a device and return a handle to it. +* +* @param pDeviceHandle [out] The opened device handle. If the function fails, NULL is returned. +* @param pDeviceConfig [in] The requested device configuration mode. Contains the mode (read/write) and the target connection string. +*/ +XN_DEVICE_INTERFACE_FUNCTION(Create, (XnDeviceHandle* pDeviceHandle, const XnDeviceConfig* pDeviceConfig)) + +/** +* Destroys a previously created device. +* +* @param pDeviceHandle [in/out] The requested device handle. +*/ +XN_DEVICE_INTERFACE_FUNCTION(Destroy, (XnDeviceHandle* pDeviceHandle)) + +/** +* Returns the types of the streams supported by this device. +* +* @param DeviceHandle [in] The requested device handle. +* @param aStreamName [in/out] An array of stream names. Will be filled by the function. +* @param pnStreamNamesCount [in/out] The size of the array. Upon successful return, will contain the number of elements written to the array. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetSupportedStreams,(const XnDeviceHandle DeviceHandle, const XnChar** aStreamName, XnUInt32* pnStreamNamesCount)) + +/** +* Creates a new stream in the device. +* +* @param DeviceHandle [in] The requested device handle. +* @param StreamType [in] The type of the stream to create (one of the types returned by XnDeviceEnumerateStreams). +* @param StreamName [in] A name for the new stream. +* @param pInitialValues [in] [Optional] A set of initial values for properties. +*/ +XN_DEVICE_INTERFACE_FUNCTION(CreateStream,(const XnDeviceHandle DeviceHandle, const XnChar* StreamType, const XnChar* StreamName, const XnPropertySet* pInitialValues)) + +/** +* Destroys a previously created stream. +* +* @param DeviceHandle [in] The requested device handle. +* @param StreamName [in] The name of the stream to destroy. +*/ +XN_DEVICE_INTERFACE_FUNCTION(DestroyStream,(const XnDeviceHandle DeviceHandle, const XnChar* StreamName)) + +/** +* Opens a stream for I/O operations. +* +* @param DeviceHandle [in] The requested device handle. +* @param StreamName [in] The name of the stream to open. +*/ +XN_DEVICE_INTERFACE_FUNCTION(OpenStream,(const XnDeviceHandle DeviceHandle, const XnChar* StreamName)) + +/** +* Closes an open stream. +* +* @param DeviceHandle [in] The requested device handle. +* @param StreamName [in] The name of the stream to close. +*/ +XN_DEVICE_INTERFACE_FUNCTION(CloseStream,(const XnDeviceHandle DeviceHandle, const XnChar* StreamName)) + +/** +* Opens all closed streams. +* +* @param DeviceHandle [in] The requested device handle. +*/ +XN_DEVICE_INTERFACE_FUNCTION(OpenAllStreams,(const XnDeviceHandle DeviceHandle)) + +/** +* Closes all open streams. +* +* @param DeviceHandle [in] The requested device handle. +*/ +XN_DEVICE_INTERFACE_FUNCTION(CloseAllStreams,(const XnDeviceHandle DeviceHandle)) + +/** +* Get a list of all the streams that exist in the device. +* +* @param DeviceHandle [in] The requested device handle. +* @param pstrStreamNames [in/out] An array of stream names. Will be filled by the function. +* @param pnArraySize [in/out] The size of the array. Upon successful return, will contain the number of elements written to the array. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetStreamNames,(const XnDeviceHandle DeviceHandle, const XnChar** pstrStreamNames, XnUInt32* pnArraySize)) + +/** +* Checks if a specific module exists in this device. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] The name of the module to look for. +* @param pbDoesExist [out] TRUE if the module exists, FALSE otherwise. +*/ +XN_DEVICE_INTERFACE_FUNCTION(DoesModuleExist,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, XnBool* pbDoesExist)) + +/** +* Registers to the event of streams change (stream created / destroyed) +* +* @param DeviceHandle [in] The requested device handle. +* @param Handler [in] A pointer to the function that will handle the event. +* @param pCookie [in] User cookie that will be passed as an argument to the event handler. +* @param hCallback [out] A handle for unregister. +*/ +XN_DEVICE_INTERFACE_FUNCTION(RegisterToStreamsChange,(const XnDeviceHandle DeviceHandle, XnDeviceOnStreamsChangedEventHandler Handler, void* pCookie, XnCallbackHandle& hCallback)) + +/** +* Unregisters from the event of streams change (stream created / destroyed) +* +* @param DeviceHandle [in] The requested device handle. +* @param hCallback [in] The handle returned from RegisterToStreamsChange. +*/ +XN_DEVICE_INTERFACE_FUNCTION(UnregisterFromStreamsChange,(const XnDeviceHandle DeviceHandle, XnCallbackHandle hCallback)) + +/** +* Creates a stream data object for the requested stream. +* +* @param DeviceHandle [in] The requested device handle. +* @param StreamName [in] The requested stream. +* @param ppStreamData [out] The created stream data object. +*/ +XN_DEVICE_INTERFACE_FUNCTION(CreateStreamData,(const XnDeviceHandle DeviceHandle, const XnChar* StreamName, XnStreamData** ppStreamOutput)) + +/** +* Destroys a stream output object that was previously created using CreateStreamData. +* +* @param DeviceHandle [in] The requested device handle. +* @param ppStreamData [in] The stream output object to destroy. +*/ +XN_DEVICE_INTERFACE_FUNCTION(DestroyStreamData,(XnStreamData** ppStreamData)) + +/** +* Registers to the event of new data from a stream. +* +* @param DeviceHandle [in] The requested device handle. +* @param Handler [in] A pointer to the function that will handle the event. +* @param pCookie [in] User cookie that will be passed as an argument to the event handler. +* @param hCallback [out] A handle for unregister. +*/ +XN_DEVICE_INTERFACE_FUNCTION(RegisterToNewStreamData,(const XnDeviceHandle DeviceHandle, XnDeviceOnNewStreamDataEventHandler Handler, void* pCookie, XnCallbackHandle& hCallback)) + +/** +* Unregisters from the event of new data from a stream. +* +* @param DeviceHandle [in] The requested device handle. +* @param hCallback [in] The handle returned from RegisterToNewStreamData. +*/ +XN_DEVICE_INTERFACE_FUNCTION(UnregisterFromNewStreamData,(const XnDeviceHandle DeviceHandle, XnCallbackHandle hCallback)) + +/** +* Checks if new data is available from stream. +* +* @param DeviceHandle [in] The requested device handle. +* @param StreamName [in] The name of the stream to check. +* @param pbNewDataAvailable [out] TRUE if new data is available, FALSE otherwise. +*/ +XN_DEVICE_INTERFACE_FUNCTION(IsNewDataAvailable,(const XnDeviceHandle DeviceHandle, const XnChar* StreamName, XnBool* pbNewDataAvailable, XnUInt64* pnTimestamp)) + +/** +* Waits for new data to be available from requested stream, and then return it. +* +* @param DeviceHandle [in] The requested device handle. +* @param pStreamOutput [in/out] A stream output object. The function will use the stream output object to determine which stream to read. +*/ +XN_DEVICE_INTERFACE_FUNCTION(ReadStream,(const XnDeviceHandle DeviceHandle, XnStreamData* pStreamOutput)) + +/** +* Waits for new data from the primary stream to be available, and then reads all requested streams. +* +* @param DeviceHandle [in] The requested device handle. +* @param pStreamOutputSet [in/out] A set of stream output objects. +*/ +XN_DEVICE_INTERFACE_FUNCTION(Read,(const XnDeviceHandle DeviceHandle, XnStreamDataSet* pStreamOutputSet)) + +/** +* Writes a single stream data to the device. +* +* @param DeviceHandle [in] The requested device handle. +* @param pStreamOutput [in] A stream output object. +*/ +XN_DEVICE_INTERFACE_FUNCTION(WriteStream,(const XnDeviceHandle DeviceHandle, XnStreamData* pStreamOutput)) + +/** +* Writes multiple streams to the device. +* +* @param DeviceHandle [in] The requested device handle. +* @param pStreamOutputSet [in] A set of stream output objects. +*/ +XN_DEVICE_INTERFACE_FUNCTION(Write,(const XnDeviceHandle DeviceHandle, XnStreamDataSet* pStreamOutputSet)) + +/** +* Gets current position of the device. +* +* @param DeviceHandle [in] The requested device handle. +* @param pnTimestamp [out] Current device timestamp. +*/ +XN_DEVICE_INTERFACE_FUNCTION(Tell,(const XnDeviceHandle DeviceHandle, XnUInt64* pnTimestamp)) + +/** +* Seeks the device to the requested position. +* +* @param DeviceHandle [in] The requested device handle. +* @param nTimestamp [in] Requested device timestamp. +*/ +XN_DEVICE_INTERFACE_FUNCTION(Seek,(const XnDeviceHandle DeviceHandle, XnUInt64 nTimestamp)) + +/** +* Gets current frame position of the device. +* +* @param DeviceHandle [in] The requested device handle. +* @param pnFrameID [out] Current device frame. +*/ +XN_DEVICE_INTERFACE_FUNCTION(TellFrame,(const XnDeviceHandle DeviceHandle, XnUInt32* pnFrameID)) + +/** +* Seeks the device to the requested frame position. +* +* @param DeviceHandle [in] The requested device handle. +* @param nFrameID [in] Requested device frame. +*/ +XN_DEVICE_INTERFACE_FUNCTION(SeekFrame,(const XnDeviceHandle DeviceHandle, XnUInt32 nFrameID)) + +/** +* Checks if a specific property exists in a module. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param pbDoesExist [out] TRUE if the property exists, FALSE otherwise. +*/ +XN_DEVICE_INTERFACE_FUNCTION(DoesPropertyExist,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, XnBool* pbDoesExist)) + +/** +* Returns the type of a specific property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param pnType [out] Type of this property. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetPropertyType,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, XnPropertyType* pnType)) + +/** +* Sets the value of an int property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param nValue [in] New requested value. +*/ +XN_DEVICE_INTERFACE_FUNCTION(SetIntProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, XnUInt64 nValue)) + +/** +* Sets the value of a real property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param dValue [in] New requested value. +*/ +XN_DEVICE_INTERFACE_FUNCTION(SetRealProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, XnDouble dValue)) + +/** +* Sets the value of a string property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param csValue [in] New requested value. +*/ +XN_DEVICE_INTERFACE_FUNCTION(SetStringProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, const XnChar* csValue)) + +/** +* Sets the value of a general property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param Value [in] New requested value. +*/ +XN_DEVICE_INTERFACE_FUNCTION(SetGeneralProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, OniGeneralBuffer Value)) + +/** +* Gets the value of an int property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param pnValue [out] Current value. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetIntProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, XnUInt64* pnValue)) + +/** +* Gets the value of a real property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param pdValue [out] Current value. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetRealProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, XnDouble* pdValue)) + +/** +* Gets the value of a string property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param csValue [in/out] Current value. The passed buffer should be of size XN_DEVICE_MAX_STRING_LENGTH. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetStringProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, XnChar* csValue)) + +/** +* Gets the value of a general property. +* +* @param DeviceHandle [in] The requested device handle. +* @param ModuleName [in] Name of the module. +* @param PropertyName [in] Name of the property to change. +* @param pValue [out] A buffer to fill. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetGeneralProperty,(const XnDeviceHandle DeviceHandle, const XnChar* ModuleName, const XnChar* PropertyName, const OniGeneralBuffer* pValue)) + +/** +* Loads configuration from INI file. +* +* @param DeviceHandle [in] The requested device handle. +* @param csINIFilePath [in] A path to the INI file. +* @param csSectionName [in] The name of the section containing configuration. +*/ +XN_DEVICE_INTERFACE_FUNCTION(LoadConfigFromFile,(const XnDeviceHandle DeviceHandle, const XnChar* csINIFilePath, const XnChar* csSectionName)) + +/** +* Batch-Configures device. All the properties in the set will be set as a single transaction. +* +* @param DeviceHandle [in] The requested device handle. +* @param pChangeSet [in] A set of properties to be changed. +*/ +XN_DEVICE_INTERFACE_FUNCTION(BatchConfig,(const XnDeviceHandle DeviceHandle, const XnPropertySet* pChangeSet)) + +/** +* Gets all the properties of a device. +* +* @param DeviceHandle [in] The requested device handle. +* @param pPropertySet [in] A property set to be filled with all the properties. +* @param bNoStreams [in] When TRUE, only modules will be returned. +* @param strModule [in] If provided, only this module's properties will be returned. +*/ +XN_DEVICE_INTERFACE_FUNCTION(GetAllProperties,(const XnDeviceHandle DeviceHandle, XnPropertySet* pPropertySet, XnBool bNoStreams, const XnChar* strModule)) + +/** +* Registers an event handler to the Property Changed event of a specific property. +* +* @param DeviceHandle [in] The requested device handle. +* @param Module [in] Name of the module. +* @param PropertyName [in] Name of the property to register to. +* @param Handler [in] A pointer to the function that will handle the event. +* @param pCookie [in] User cookie that will be passed as an argument to the event handler. +* @param hCallback [out] A handle for unregister. +*/ +XN_DEVICE_INTERFACE_FUNCTION(RegisterToPropertyChange,(const XnDeviceHandle DeviceHandle, const XnChar* Module, const XnChar* PropertyName, XnDeviceOnPropertyChangedEventHandler Handler, void* pCookie, XnCallbackHandle& hCallback)) + +/** +* Unregisters an event handler from the Property Changed event. +* +* @param DeviceHandle [in] The requested device handle. +* @param Module [in] Name of the module. +* @param PropertyName [in] Name of the property to register to. +* @param hCallback [in] The handle returned from RegisterToNewStreamData. +*/ +XN_DEVICE_INTERFACE_FUNCTION(UnregisterFromPropertyChange,(const XnDeviceHandle DeviceHandle, const XnChar* Module, const XnChar* PropertyName, XnCallbackHandle hCallback)) + diff --git a/Source/Drivers/PS1080/Include/XnDeviceProxy.h b/Source/Drivers/PS1080/Include/XnDeviceProxy.h new file mode 100644 index 0000000..baa1cd2 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnDeviceProxy.h @@ -0,0 +1,98 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEVICE_PROXY_H__ +#define __XN_DEVICE_PROXY_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Exported Functions +//--------------------------------------------------------------------------- + +/*****************************/ +/* XnDevice interface */ +/*****************************/ +#define XN_DEVICE_PROXY_PROTO_APPEND(prefix, name) prefix ## name +#define XN_DEVICE_PROXY_PROTO(name) XN_DEVICE_PROXY_PROTO_APPEND(XnDeviceProxy, name) +#define XN_DEVICE_INTERFACE_FUNCTION(name, sig) XN_DDK_API XnStatus XN_DEVICE_PROXY_PROTO(name)sig; +#include +#undef XN_DEVICE_INTERFACE_FUNCTION + +/****************************/ +/* Specific Proxy Functions */ +/****************************/ + +/** +* Gets a list of supported devices, meaning, devices loaded by device manager. +* +* @param aDeviceDefinitions [in] An array of XnDeviceDefinition to be filled with information. +* @param pnCount [in/out] In: the size of the array. Out: the number of elements filled in the array. +*/ +XN_DDK_API XnStatus XnDeviceProxyGetDeviceList(XnDeviceDefinition* aDeviceDefinitions, XnUInt32* pnCount); + +/** +* Enumerates a specific device, by device name. +* +* @param csDeviceName [in] The name of the device to enumerate. +* @param aConnectionStrings [in] An array to be filled with connection strings. +* @param pnCount [in/out] In: the size of the array. Out: the number of elements filled in the array. +*/ +XN_DDK_API XnStatus XnDeviceProxyEnumerateDeviceByName(const XnChar* csDeviceName, XnConnectionString* aConnectionStrings, XnUInt32* pnCount); + +/** +* Creates a device by name. +* +* @param csDeviceName [in] The name of the device to create. The special value "Auto" will create any available device. +* @param pDeviceHandle [out] The opened device handle. If the function fails, NULL is returned. +* @param pDeviceConfig [in] The requested device configuration mode. Contains the mode (read/write) and the target connection string. +*/ +XN_DDK_API XnStatus XnDeviceProxyCreateDeviceByName(const XnChar* csDeviceName, XnDeviceHandle* pDeviceHandle, const XnDeviceConfig* pDeviceConfig); + +/** +* Creates a device by definitions in INI file. +* +* @param strIniFileName [in] INI file to use for initialization. +* @param strSectionName [in] section name in INI file that describes the device. +* @param pDeviceHandle [out] The opened device handle. If the function fails, NULL is returned. +* @param pInitialValues [in] Optional. A set of initial values to be used. +*/ +XN_DDK_API XnStatus XnDeviceProxyCreateDeviceByINIFile(const XnChar* strIniFileName, const XnChar* strSectionName, XnDeviceHandle* pDeviceHandle, const XnPropertySet* pInitialValues); + +/** +* Destroys a stream output object that was previously created using CreateStreamOutput. +* +* @param csDeviceName [in] The name of the device that created this object. +* @param ppStreamOutput [in] The stream output object to destroy. +*/ +XN_DDK_API XnStatus XnDeviceProxyDestroyStreamOutputByName(const XnChar* csDeviceName, XnStreamData** ppStreamOutput); + +/** +* Gets the name of an opened device. +* +* @param DeviceHandle [in] The requested device handle. +* @param csDeviceName [in/out] A string to be filled with its name. The buffer must be at least XN_DEVICE_MAX_STRING_LENGTH long. +*/ +XN_DDK_API XnStatus XnDeviceProxyGetDeviceName(XnDeviceHandle DeviceHandle, XnChar* csDeviceName); + +#endif //__XN_DEVICE_PROXY_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Include/XnFormatsStatus.h b/Source/Drivers/PS1080/Include/XnFormatsStatus.h new file mode 100644 index 0000000..a229760 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnFormatsStatus.h @@ -0,0 +1,63 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_FORMATS_STATUS_H_ +#define _XN_FORMATS_STATUS_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +XN_PS_STATUS_MESSAGE_MAP_START(XN_ERROR_GROUP_FORMATS) +XN_STATUS_MESSAGE(XN_STATUS_FORMATS_NOT_INIT, "Xiron Formats library was not initialized!") +XN_STATUS_MESSAGE(XN_STATUS_FORMATS_ALREADY_INIT, "Xiron Formats library was already initialized!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_DEPTH_FORMAT, "Invalid Xiron I/O stream depth format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_IMAGE_FORMAT, "Invalid Xiron I/O stream image format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_MISC_FORMAT, "Invalid Xiron I/O stream misc format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_FORMAT, "Invalid Xiron I/O stream audio format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_COMPRESSION_INIT_FAILED, "Xiron I/O compression initialization failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_COMPRESSION_FAILED, "Xiron I/O compression failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_DECOMPRESSION_FAILED, "Xiron I/O decompression failed!") +XN_STATUS_MESSAGE(XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL, "The compressed input buffer is too small to be valid!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_COMPRESSED_BUFFER_SIZE, "Invalid compressed buffer size!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_DEPTH_COMPRESSION_FORMAT, "Invalid depth compression format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_IMAGE_COMPRESSION_FORMAT, "Invalid image stream compression format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_MISC_COMPRESSION_FORMAT, "Invalid misc stream compression format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_COMPRESSION_FORMAT, "Invalid audio stream compression format!") +XN_STATUS_MESSAGE(XN_STATUS_IO_UNSUPPORTED_COMPRESSION_FORMAT, "This compression format is no longer supported!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_PACKED_BUFFER, "Invalid Xiron I/O packed stream buffer!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_HEADER, "Invalid Xiron I/O stream header!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_DEPTH_BUFFER, "Invalid Xiron I/O stream depth buffer!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_IMAGE_BUFFER, "Invalid Xiron I/O stream image buffer!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_MISC_BUFFER, "Invalid Xiron I/O stream misc buffer!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_BUFFER, "Invalid Xiron I/O stream audio buffer!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_DEPTH_BUFFER_SIZE, "Invalid Xiron I/O stream depth buffer size!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_IMAGE_BUFFER_SIZE, "Invalid Xiron I/O stream image buffer size!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_MISC_BUFFER_SIZE, "Invalid Xiron I/O stream misc buffer size!") +XN_STATUS_MESSAGE(XN_STATUS_IO_INVALID_STREAM_AUDIO_BUFFER_SIZE, "Invalid Xiron I/O stream audio buffer size!") +XN_STATUS_MESSAGE(XN_STATUS_INVALID_OUTPUT_FORMAT_FOR_RESOLUTION, "Pixel format is not supported for this resolution!") +XN_PS_STATUS_MESSAGE_MAP_END(XN_ERROR_GROUP_FORMATS) + +#endif //_XN_FORMATS_STATUS_H_ diff --git a/Source/Drivers/PS1080/Include/XnIOFileStream.h b/Source/Drivers/PS1080/Include/XnIOFileStream.h new file mode 100644 index 0000000..996177c --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnIOFileStream.h @@ -0,0 +1,53 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_IO_FILE_STREAM_H__ +#define __XN_IO_FILE_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnIOFileStream : public XnIOStream +{ +public: + XnIOFileStream(const XnChar* pcsFileName, XnUInt32 nFlags); + ~XnIOFileStream() { Free(); } + + virtual XnStatus WriteData(const XnUChar* pData, XnUInt32 nDataSize); + virtual XnStatus ReadData(XnUChar* pData, XnUInt32 nDataSize); + virtual XnStatus Init(); + virtual XnStatus Free(); + + XnStatus Tell(XnUInt64* pnOffset); + XnStatus Seek(XnUInt64 nOffset); + +private: + const XnChar* m_pcsFileName; + XnUInt32 m_nFlags; + XN_FILE_HANDLE m_hFile; +}; + +#endif //__XN_IO_FILE_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Include/XnIONetworkStream.h b/Source/Drivers/PS1080/Include/XnIONetworkStream.h new file mode 100644 index 0000000..8bad497 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnIONetworkStream.h @@ -0,0 +1,53 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_IO_NETWORK_STREAM_H__ +#define __XN_IO_NETWORK_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnIONetworkStream : public XnIOStream +{ +public: + XnIONetworkStream(XN_SOCKET_HANDLE hSocket); + ~XnIONetworkStream() { Free(); } + + virtual XnStatus WriteData(const XnUChar* pData, XnUInt32 nDataSize); + virtual XnStatus ReadData(XnUChar* pData, XnUInt32 nDataSize); + virtual XnStatus Init(); + virtual XnStatus Free(); + inline XnBool IsConnected() const { return m_bIsConnected; } + void SetReadTimeout(XnUInt32 nMicrosecondsReadTimeout); + +private: + XnUInt32 m_nReadTimeout; + XN_SOCKET_HANDLE m_hSocket; + XnBool m_bIsConnected; + +}; + +#endif //__XN_IO_NETWORK_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Include/XnIOStream.h b/Source/Drivers/PS1080/Include/XnIOStream.h new file mode 100644 index 0000000..b919409 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnIOStream.h @@ -0,0 +1,46 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_IO_STREAM_H__ +#define __XN_IO_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnIOStream +{ +public: + XnIOStream() {} + virtual ~XnIOStream() {} + + virtual XnStatus WriteData(const XnUChar* pData, XnUInt32 nDataSize) = 0; + virtual XnStatus ReadData(XnUChar* pData, XnUInt32 nDataSize) = 0; + virtual XnStatus Init() { return XN_STATUS_OK; } + virtual XnStatus Free() { return XN_STATUS_OK; } +}; + +#endif //__XN_IO_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Include/XnPlatformBC.h b/Source/Drivers/PS1080/Include/XnPlatformBC.h new file mode 100644 index 0000000..669303f --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnPlatformBC.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PS_PLATFORM_H__ +#define __XN_PS_PLATFORM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +#define _XN_DEPRECATED_TYPE(newType, oldType) \ + XN_API_DEPRECATED("Please use " XN_STRINGIFY(newType) " instead.") typedef newType oldType; + +_XN_DEPRECATED_TYPE(XnChar, XN_CHAR) +_XN_DEPRECATED_TYPE(XnUChar, XN_UCHAR) +_XN_DEPRECATED_TYPE(XnInt8, XN_INT8) +_XN_DEPRECATED_TYPE(XnUInt8, XN_UINT8) +_XN_DEPRECATED_TYPE(XnInt16, XN_INT16) +_XN_DEPRECATED_TYPE(XnUInt16, XN_UINT16) +_XN_DEPRECATED_TYPE(XnInt32, XN_INT32) +_XN_DEPRECATED_TYPE(XnUInt32, XN_UINT32) +_XN_DEPRECATED_TYPE(XnInt64, XN_INT64) +_XN_DEPRECATED_TYPE(XnUInt64, XN_UINT64) +_XN_DEPRECATED_TYPE(XnFloat, XN_FLOAT) +_XN_DEPRECATED_TYPE(XnDouble, XN_DOUBLE) +_XN_DEPRECATED_TYPE(XnBool, XN_BOOL) +_XN_DEPRECATED_TYPE(XnWChar, XN_WCHAR) +_XN_DEPRECATED_TYPE(XnInt, XN_LONG) +_XN_DEPRECATED_TYPE(XnUInt, XN_ULONG) + +XN_API_DEPRECATED("Please use OniDepthPixel instead") typedef XnUInt16 XN_DEPTH_TYPE; +XN_API_DEPRECATED("Please use XnRGB24Pixel instead") typedef XnUChar XN_IMAGE_TYPE; + +#endif // __XN_PS_PLATFORM_H__ diff --git a/Source/Drivers/PS1080/Include/XnPropertySet.h b/Source/Drivers/PS1080/Include/XnPropertySet.h new file mode 100644 index 0000000..b6ae158 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnPropertySet.h @@ -0,0 +1,250 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PROPERTY_SET_H__ +#define __XN_PROPERTY_SET_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +/** The type of the property. */ +typedef enum XnPropertyType +{ + XN_PROPERTY_TYPE_INTEGER, + XN_PROPERTY_TYPE_REAL, + XN_PROPERTY_TYPE_STRING, + XN_PROPERTY_TYPE_GENERAL, +} XnPropertyType; + +struct XnPropertySet; // Forward Declaration +typedef struct XnPropertySet XnPropertySet; + +struct XnPropertySetModuleEnumerator; // Forward Declaration +typedef struct XnPropertySetModuleEnumerator XnPropertySetModuleEnumerator; + +struct XnPropertySetEnumerator; // Forward Declaration +typedef struct XnPropertySetEnumerator XnPropertySetEnumerator; + +//--------------------------------------------------------------------------- +// Exported functions +//--------------------------------------------------------------------------- + +/** +* Creates a new property set. +* +* @param ppSet [out] A pointer to the new set. +*/ +XnStatus XnPropertySetCreate(XnPropertySet** ppSet); + +/** +* Destroys a previously created property set. +* +* @param ppSet [in/out] A pointer to the set. +*/ +XnStatus XnPropertySetDestroy(XnPropertySet** ppSet); + +/** +* Clears a property set from all the properties. +* +* @param pSet [in] The property set. +*/ +XnStatus XnPropertySetClear(XnPropertySet* pSet); + +/** +* Adds a module to the property set. +* +* @param pSet [in] The property set. +* @param strModuleName [in] Name of the module to add. +*/ +XnStatus XnPropertySetAddModule(XnPropertySet* pSet, const XnChar* strModuleName); + +/** +* Removes a module from the property set. +* +* @param pSet [in] The property set. +* @param strModuleName [in] Name of the module to remove. +*/ +XnStatus XnPropertySetRemoveModule(XnPropertySet* pSet, const XnChar* strModuleName); + +/** +* Adds an integer property to the property set. +* +* @param pSet [in] The property set. +* @param strModuleName [in] Name of the module. +* @param strModuleName [in] Name of the property to add. +* @param nValue [in] Value for that property. +*/ +XnStatus XnPropertySetAddIntProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, XnUInt64 nValue); + +/** +* Adds an real property to the property set. +* +* @param pSet [in] The property set. +* @param strModuleName [in] Name of the module. +* @param strModuleName [in] Name of the property to add. +* @param dValue [in] Value for that property. +*/ +XnStatus XnPropertySetAddRealProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, XnDouble dValue); + +/** +* Adds an string property to the property set. +* +* @param pSet [in] The property set. +* @param strModuleName [in] Name of the module. +* @param strModuleName [in] Name of the property to add. +* @param strValue [in] Value for that property. +*/ +XnStatus XnPropertySetAddStringProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, const XnChar* strValue); + +/** +* Adds an general property to the property set. +* +* @param pSet [in] The property set. +* @param strModuleName [in] Name of the module. +* @param strModuleName [in] Name of the property to add. +* @param pgbValue [in] Value for that property. +*/ +XnStatus XnPropertySetAddGeneralProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId, const OniGeneralBuffer* pgbValue); + +/** +* Removes a property from the property set. +* +* @param pSet [in] The property set. +* @param strModuleName [in] Name of the module. +* @param strModuleName [in] Name of the property to remove. +*/ +XnStatus XnPropertySetRemoveProperty(XnPropertySet* pSet, const XnChar* strModuleName, XnUInt32 propertyId); + +/** +* Gets a modules enumerator. This enumerator should be freed using XnPropertySetModuleEnumeratorFree. +* +* @param pSet [in] The property set. +* @param ppEnumerator [out] The created enumerator. +*/ +XnStatus XnPropertySetGetModuleEnumerator(const XnPropertySet* pSet, XnPropertySetModuleEnumerator** ppEnumerator); + +/** +* Frees a previously created module enumerator. +* +* @param ppEnumerator [in/out] The enumerator. +*/ +XnStatus XnPropertySetModuleEnumeratorFree(XnPropertySetModuleEnumerator** ppEnumer); + +/** +* Moves the enumerator to the next module. This function must be called *before* getting current. +* +* @param pEnumerator [in] The enumerator. +* @param pbEnd [out] TRUE if the enumerator has reached the end of the collection. +*/ +XnStatus XnPropertySetModuleEnumeratorMoveNext(XnPropertySetModuleEnumerator* pEnumerator, XnBool* pbEnd); + +/** +* Gets the current module name from the enumerator. +* +* @param pEnumerator [in] The enumerator. +* @param pstrModuleName [out] The name of the current module. +*/ +XnStatus XnPropertySetModuleEnumeratorGetCurrent(const XnPropertySetModuleEnumerator* pEnumer, const XnChar** pstrModuleName); + +/** +* Gets a property enumerator. This enumerator must be freed using XnPropertySetEnumeratorFree. +* +* @param pSet [in] The property set. +* @param ppEnumerator [in/out] The enumerator. +* @param strModule [in] Optional. When provided, only properties of this module will be enumerated. +*/ +XnStatus XnPropertySetGetEnumerator(const XnPropertySet* pSet, XnPropertySetEnumerator** ppEnumerator, const XnChar* strModule = NULL); + +/** +* Finds a property according to its name and module, and returns an enumerator to it. +* This enumerator must be freed using XnPropertySetEnumeratorFree. +* +* @param pSet [in] The property set. +* @param strModule [in] The module name. +* @param strProp [in] The property name. +* @param ppEnumerator [in/out] The enumerator. +*/ +XnStatus XnPropertySetFindProperty(const XnPropertySet* pSet, const XnChar* strModule, XnUInt32 propertyId, XnPropertySetEnumerator** ppEnumerator); + +/** +* Frees a previously created properties enumerator. +* +* @param ppEnumerator [in/out] The enumerator. +*/ +XnStatus XnPropertySetEnumeratorFree(XnPropertySetEnumerator** ppEnumerator); + +/** +* Moves the enumerator to the next property. This function must be called *before* getting current. +* +* @param pEnumerator [in] The enumerator. +* @param pbEnd [out] TRUE if the enumerator has reached the end of the collection. +*/ +XnStatus XnPropertySetEnumeratorMoveNext(XnPropertySetEnumerator* pEnumerator, XnBool* pbEnd); + +/** +* Gets information regarding the current property. +* +* @param pEnumerator [in] The enumerator. +* @param pnType [out] The type of the current property. +* @param pstrModule [out] The module of the current property. +* @param pstrProp [out] The name of the current property. +*/ +XnStatus XnPropertySetEnumeratorGetCurrentPropertyInfo(const XnPropertySetEnumerator* pEnumerator, XnPropertyType* pnType, const XnChar** pstrModule, XnUInt32* pPropertyId); + +/** +* Gets the current integer value. +* +* @param pEnumerator [in] The enumerator. +* @param pnValue [out] The value of the property. +*/ +XnStatus XnPropertySetEnumeratorGetIntValue(const XnPropertySetEnumerator* pEnumerator, XnUInt64* pnValue); + +/** +* Gets the current real property. +* +* @param pEnumerator [in] The enumerator. +* @param pdValue [out] The value of the property. +*/ +XnStatus XnPropertySetEnumeratorGetRealValue(const XnPropertySetEnumerator* pEnumerator, XnDouble* pdValue); + +/** +* Gets the current string property. +* +* @param pEnumerator [in] The enumerator. +* @param pstrValue [out] The value of the property. +*/ +XnStatus XnPropertySetEnumeratorGetStringValue(const XnPropertySetEnumerator* pEnumerator, const XnChar** pstrValue); + +/** +* Gets the current general property. +* +* @param pEnumerator [in] The enumerator. +* @param pgbValue [out] The value of the property. +*/ +XnStatus XnPropertySetEnumeratorGetGeneralValue(const XnPropertySetEnumerator* pEnumerator, OniGeneralBuffer* pgbValue); + +#endif //__XN_PROPERTY_SET_H__ diff --git a/Source/Drivers/PS1080/Include/XnPsVersion.h b/Source/Drivers/PS1080/Include/XnPsVersion.h new file mode 100644 index 0000000..78dbc75 --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnPsVersion.h @@ -0,0 +1,57 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_PS_VERSION_H_ +#define _XN_PS_VERSION_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +/** Xiron major version. */ +#define XN_PS_MAJOR_VERSION 5 +/** Xiron minor version. */ +#define XN_PS_MINOR_VERSION 1 +/** Xiron maintenance version. */ +#define XN_PS_MAINTENANCE_VERSION 4 +/** Xiron build version. */ +#define XN_PS_BUILD_VERSION 1 + +/** Xiron version (in brief string format): "Major.Minor.Maintenance (Build)" */ +#define XN_PS_BRIEF_VERSION_STRING \ + XN_STRINGIFY(XN_PS_MAJOR_VERSION) "." \ + XN_STRINGIFY(XN_PS_MINOR_VERSION) "." \ + XN_STRINGIFY(XN_PS_MAINTENANCE_VERSION) \ + " (Build " XN_STRINGIFY(XN_PS_BUILD_VERSION) ")" + +/** Xiron version (in numeric format): (Xiron major version * 100000000 + Xiron minor version * 1000000 + Xiron maintenance version * 10000 + Xiron build version). */ +#define XN_PS_VERSION (XN_PS_MAJOR_VERSION*100000000 + XN_PS_MINOR_VERSION*1000000 + XN_PS_MAINTENANCE_VERSION*10000 + XN_PS_BUILD_VERSION) + +/** Xiron version (in string format): "Major.Minor.Maintenance.Build-Platform (MMM DD YYYY HH:MM:SS)". */ +#define XN_PS_VERSION_STRING \ + XN_PS_BRIEF_VERSION_STRING "-" \ + XN_PLATFORM_STRING " (" XN_TIMESTAMP ")" + +#endif //_XN_VERSION_H_ + diff --git a/Source/Drivers/PS1080/Include/XnStreamFormats.h b/Source/Drivers/PS1080/Include/XnStreamFormats.h new file mode 100644 index 0000000..f0600cd --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnStreamFormats.h @@ -0,0 +1,50 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_STREAM_FORMATS_H__ +#define __XN_STREAM_FORMATS_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Formats +//--------------------------------------------------------------------------- + +typedef enum +{ + /** Data is stored uncompressed. */ + XN_COMPRESSION_NONE = 0, + /** Data is compressed using PS lossless 16-bit depth compression. */ + XN_COMPRESSION_16Z = 1, + /** Data is compressed using PS lossless 16-bit depth compression with embedded tables. */ + XN_COMPRESSION_16Z_EMB_TABLE = 2, + /** Data is compressed using PS lossless 8-bit image compression (for grayscale). */ + XN_COMPRESSION_COLOR_8Z = 3, + /** Data is compressed using JPEG. */ + XN_COMPRESSION_JPEG = 4, + /** Data is packed in 10-bit values. */ + XN_COMPRESSION_10BIT_PACKED = 5, +} XnCompressionFormats; + +#endif //__XN_STREAM_FORMATS_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Include/XnStreamParams.h b/Source/Drivers/PS1080/Include/XnStreamParams.h new file mode 100644 index 0000000..d2c158e --- /dev/null +++ b/Source/Drivers/PS1080/Include/XnStreamParams.h @@ -0,0 +1,260 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_STREAM_PARAMS_H_ +#define _XN_STREAM_PARAMS_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Modules Names +//--------------------------------------------------------------------------- +#define XN_MODULE_NAME_DEVICE "Device" + +#define XN_MODULE_NAME_FIXED_PARAMS "FixedParams" +#define XN_MODULE_NAME_SHIFTS "Shifts" + +//--------------------------------------------------------------------------- +// Streams Types +//--------------------------------------------------------------------------- +#define XN_STREAM_TYPE_DEPTH "Depth" +#define XN_STREAM_TYPE_IMAGE "Image" +#define XN_STREAM_TYPE_IR "IR" +#define XN_STREAM_TYPE_AUDIO "Audio" + +//--------------------------------------------------------------------------- +// Streams Names +//--------------------------------------------------------------------------- +#define XN_STREAM_NAME_DEPTH XN_STREAM_TYPE_DEPTH +#define XN_STREAM_NAME_IMAGE XN_STREAM_TYPE_IMAGE +#define XN_STREAM_NAME_IR XN_STREAM_TYPE_IR +#define XN_STREAM_NAME_AUDIO XN_STREAM_TYPE_AUDIO + +/* Internal properties - using same structure as private properties - 0x1080FFXX */ +enum +{ + //--------------------------------------------------------------------------- + // General Properties + //--------------------------------------------------------------------------- + XN_STREAM_PROPERTY_TYPE = 0x1080FF00, // "Type" + XN_STREAM_PROPERTY_IS_STREAM = 0x1080FF01, // "IsStream" + XN_STREAM_PROPERTY_IS_FRAME_BASED = 0x1080FF02, // "IsFrameBased" + XN_STREAM_PROPERTY_IS_PIXEL_BASED = 0x1080FF03, // "IsPixelBased" + XN_STREAM_PROPERTY_IS_STREAMING = 0x1080FF04, // "IsStreaming" + + /** Integer */ + XN_MODULE_PROPERTY_LOCK = 0x1080FF05, // "Lock" + + //--------------------------------------------------------------------------- + // General Stream Properties + //--------------------------------------------------------------------------- + /** Integer */ + XN_STREAM_PROPERTY_STATE = 0x1080FF10, // "State" + /** Integer */ + XN_STREAM_PROPERTY_REQUIRED_DATA_SIZE = 0x1080FF11, // "RequiredDataSize" + /** Integer (OniFormat) */ + XN_STREAM_PROPERTY_OUTPUT_FORMAT = 0x1080FF12, // "OutputFormat" + /** Integer */ + XN_STREAM_PROPERTY_BUFFER_SIZE = 0x1080FF13, // "BufferSize" + /** Boolean */ + XN_STREAM_PROPERTY_ACTUAL_READ_DATA = 0x1080FF14, // "ActualReadData" + + //--------------------------------------------------------------------------- + // Frame-Based Stream Properties (Depth, Image, IR) + //--------------------------------------------------------------------------- + /** Integer */ + XN_STREAM_PROPERTY_FPS = 0x1080FF20, // "FPS" + + //--------------------------------------------------------------------------- + // Pixel-Based Stream Properties (Depth, Image, IR) + //--------------------------------------------------------------------------- + /** XnResolutions */ + XN_STREAM_PROPERTY_RESOLUTION = 0x1080FF30, // "Resolution" + /** Integer */ + XN_STREAM_PROPERTY_X_RES = 0x1080FF31, // "XRes" + /** Integer */ + XN_STREAM_PROPERTY_Y_RES = 0x1080FF32, // "YRes" + /** Integer */ + XN_STREAM_PROPERTY_BYTES_PER_PIXEL = 0x1080FF33, // "BytesPerPixel" + /** Integer */ + XN_STREAM_PROPERTY_SUPPORT_MODES_COUNT = 0x1080FF34, // "SupportedModesCount" + /** General (XnCmosPreset array) */ + XN_STREAM_PROPERTY_SUPPORT_MODES = 0x1080FF35, // "SupportedModes" + /** OniCropping */ + XN_STREAM_PROPERTY_CROPPING = 0x1080FF36, // "Cropping" + + //--------------------------------------------------------------------------- + // Depth Specific Properties + //--------------------------------------------------------------------------- + /** unsigned long long */ + XN_STREAM_PROPERTY_MIN_DEPTH = 0x1080FF40, // "MinDepthValue" + /** unsigned long long */ + XN_STREAM_PROPERTY_MAX_DEPTH = 0x1080FF41, // "MaxDepthValue" + /** Boolean */ + XN_STREAM_PROPERTY_REGISTRATION = 0x1080FF42, // "Registration" + /** Integer */ + XN_STREAM_PROPERTY_DEVICE_MAX_DEPTH = 0x1080FF43, // "DeviceMaxDepth" + + //--------------------------------------------------------------------------- + // Image Specific Properties + //--------------------------------------------------------------------------- + /** Integer */ + XN_STREAM_PROPERTY_QUALITY = 0x1080FF51, // "Quality" + + //--------------------------------------------------------------------------- + // Audio Specific Properties + //--------------------------------------------------------------------------- + /** XnSampleRate */ + XN_STREAM_PROPERTY_SAMPLE_RATE = 0x1080FF60, // "SampleRate" + /** Integer */ + XN_STREAM_PROPERTY_LEFT_CHANNEL_VOLUME = 0x1080FF61, // "LeftChannelVolume" + /** Integer */ + XN_STREAM_PROPERTY_RIGHT_CHANNEL_VOLUME = 0x1080FF62, // "RightChannelVolume" + /** Integer */ + XN_STREAM_PROPERTY_NUMBER_OF_CHANNELS = 0x1080FF63, // "NumOfChannels" + /** Boolean */ + XN_STREAM_PROPERTY_IS_STEREO = 0x1080FF64, // "IsStereo" + /** Integer */ + XN_STREAM_PROPERTY_READ_MODE = 0x1080FF65, // "ReadMode" + /** Integer */ + XN_STREAM_PROPERTY_READ_CHUNK_SIZE = 0x1080FF66, // "ReadChunkSize" + /** Integer */ + XN_STREAM_PROPERTY_READ_SYNC = 0x1080FF67, // "AudioReadSync" + + //--------------------------------------------------------------------------- + // DeviceParams Properties + //--------------------------------------------------------------------------- + /** Integer */ + XN_MODULE_PROPERTY_NUMBER_OF_BUFFERS = 0x1080FF70, // "NumberOfBuffers" + /** Boolean */ + XN_MODULE_PROPERTY_READ_DATA = 0x1080FF71, // "ReadData" + /** Integer */ + XN_MODULE_PROPERTY_READ_WRITE_MODE = 0x1080FF72, // "ReadWriteMode" + /** Boolean */ + XN_MODULE_PROPERTY_FRAME_SYNC = 0x1080FF73, // "FrameSync" + /* XnDynamicSizeBuffer */ + XN_MODULE_PROPERTY_FIXED_PARAMS = 0x1080FF76, // "FixedParams" + /** Integer */ + XN_MODULE_PROPERTY_ERROR_STATE = 0x1080FF79, // "ErrorState" + /** Boolean */ + XN_MODULE_PROPERTY_AUDIO_SUPPORTED = 0x1080FF7D, // "AudioSupported" + /** Boolean */ + XN_MODULE_PROPERTY_IMAGE_SUPPORTED = 0x1080FF7E, // "ImageSupported" +}; + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_GAIN_AUTO 0U + +#define XN_QVGA_X_RES 320 +#define XN_QVGA_Y_RES 240 +#define XN_VGA_X_RES 640 +#define XN_VGA_Y_RES 480 +#define XN_SXGA_X_RES 1280 +#define XN_SXGA_Y_RES 1024 +#define XN_UXGA_X_RES 1600 +#define XN_UXGA_Y_RES 1200 + +//--------------------------------------------------------------------------- +// Enums - values of various properties +//--------------------------------------------------------------------------- +typedef enum XnResolutions +{ + XN_RESOLUTION_CUSTOM = -1, + XN_RESOLUTION_QVGA = 0, // 320x240 + XN_RESOLUTION_VGA = 1, // 640x480 + XN_RESOLUTION_SXGA = 2, // 1280x1024 + XN_RESOLUTION_UXGA = 3, // 1600x1200 + XN_RESOLUTION_QQVGA = 4, // 160x120 + XN_RESOLUTION_QCIF = 5, // 176x144 + XN_RESOLUTION_240P = 6, // 432x240 + XN_RESOLUTION_CIF = 7, // 352x288 + XN_RESOLUTION_WVGA = 8, // 640x360 + XN_RESOLUTION_480P = 9, // 864x480 + XN_RESOLUTION_800_448 = 10, // 800x448 + XN_RESOLUTION_SVGA = 11, // 800x600 + XN_RESOLUTION_576P = 12, // 1024x576 + XN_RESOLUTION_DV = 13, // 960x720 + XN_RESOLUTION_720P = 14, // 1280x720 + XN_RESOLUTION_1280_960 = 15, // 1280x960 +} XnResolutions; + +typedef enum XnSampleRate +{ + XN_SAMPLE_RATE_8K = 8000, + XN_SAMPLE_RATE_11K = 11025, + XN_SAMPLE_RATE_12K = 12000, + XN_SAMPLE_RATE_16K = 16000, + XN_SAMPLE_RATE_22K = 22050, + XN_SAMPLE_RATE_24K = 24000, + XN_SAMPLE_RATE_32K = 32000, + XN_SAMPLE_RATE_44K = 44100, + XN_SAMPLE_RATE_48K = 48000, +} XnSampleRate; + +typedef enum +{ + XN_MODE_PS = 0, + XN_MODE_MAINTENANCE = 1, + XN_MODE_SAFE_MODE = 2, +} XnParamCurrentMode; + +typedef enum +{ + XN_VIDEO_STREAM_OFF = 0, + XN_VIDEO_STREAM_COLOR = 1, + XN_VIDEO_STREAM_DEPTH = 2, + XN_VIDEO_STREAM_IR = 3, +} XnVideoStreamMode; + +typedef enum +{ + XN_AUDIO_STREAM_OFF = 0, + XN_AUDIO_STREAM_ON = 1, +} XnAudioStreamMode; + +//--------------------------------------------------------------------------- +// Data Structures - structures that are arguments to properties +//--------------------------------------------------------------------------- + +#pragma pack (push, 1) + +typedef struct XnDynamicSizeBuffer +{ + void* pData; + XnUInt32 nMaxSize; + XnUInt32 nDataSize; +} XnDynamicSizeBuffer; + +typedef struct XnCmosPreset +{ + XnUInt16 nFormat; + XnUInt16 nResolution; + XnUInt16 nFPS; +} XnCmosPreset; + +#pragma pack (pop) + +#endif // _XN_STREAM_PARAMS_H_ diff --git a/Source/Drivers/PS1080/Makefile b/Source/Drivers/PS1080/Makefile new file mode 100644 index 0000000..fdcc213 --- /dev/null +++ b/Source/Drivers/PS1080/Makefile @@ -0,0 +1,44 @@ +include ../../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../../Bin + +INC_DIRS = \ + . \ + Include \ + ../../../Include \ + ../../../ThirdParty/PSCommon/XnLib/Include \ + ../../../ThirdParty/LibJPEG \ + ../../DepthUtils + +SRC_FILES = \ + Core/*.cpp \ + DDK/*.cpp \ + DriverImpl/*.cpp\ + Formats/*.cpp \ + Include/*.cpp \ + Sensor/*.cpp \ + ../../../ThirdParty/LibJPEG/*.c + + +ifeq ("$(OSTYPE)","Darwin") + INC_DIRS += /opt/local/include + LIB_DIRS += /opt/local/lib + LDFLAGS += -framework CoreFoundation -framework IOKit +endif + +LIB_NAME = PS1080 + +LIB_DIRS += ../../../ThirdParty/PSCommon/XnLib/Bin/$(PLATFORM)-$(CFG) +LIB_DIRS += $(BIN_DIR)/$(PLATFORM)-$(CFG) +USED_LIBS = XnLib dl pthread DepthUtils +ifneq ("$(OSTYPE)","Darwin") + USED_LIBS += rt usb-1.0 udev +else + USED_LIBS += usb-1.0.0 +endif + +CFLAGS += -Wall + +OUT_DIR := $(OUT_DIR)/OpenNI2/Drivers + +include ../../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Source/Drivers/PS1080/PS1080.vcxproj b/Source/Drivers/PS1080/PS1080.vcxproj new file mode 100644 index 0000000..2a1154d --- /dev/null +++ b/Source/Drivers/PS1080/PS1080.vcxproj @@ -0,0 +1,699 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + Level3 + Level3 + Level3 + Level3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {9F6652AF-35F2-452E-A2D3-08D05F5C075E} + Win32Proj + PS1080 + + + + DynamicLibrary + true + Unicode + + + DynamicLibrary + true + Unicode + + + DynamicLibrary + false + true + Unicode + + + DynamicLibrary + false + true + Unicode + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;PS1080_EXPORTS;%(PreprocessorDefinitions) + .\Include;.\;..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG;..\..\DepthUtils + true + StreamingSIMDExtensions2 + true + + + + + Windows + true + XnLib.lib;DepthUtils.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + ..\..\..\Include + + + + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;PS1080_EXPORTS;%(PreprocessorDefinitions) + .\Include;.\;..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG;..\..\DepthUtils + true + StreamingSIMDExtensions2 + true + + + + + Windows + true + XnLib.lib;DepthUtils.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + ..\..\..\Include + + + + + Level4 + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;PS1080_EXPORTS;%(PreprocessorDefinitions) + .\Include;.\;..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG;..\..\DepthUtils + true + StreamingSIMDExtensions2 + true + AnySuitable + Speed + true + true + false + Fast + + + Windows + true + true + true + XnLib.lib;DepthUtils.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + ..\..\..\Include + + + + + Level4 + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;PS1080_EXPORTS;%(PreprocessorDefinitions) + .\Include;.\;..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\ThirdParty\LibJPEG;..\..\DepthUtils + true + StreamingSIMDExtensions2 + true + AnySuitable + Speed + true + true + false + Fast + + + Windows + true + true + true + XnLib.lib;DepthUtils.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + ..\..\..\Include + + + + + + \ No newline at end of file diff --git a/Source/Drivers/PS1080/PS1080.vcxproj.filters b/Source/Drivers/PS1080/PS1080.vcxproj.filters new file mode 100644 index 0000000..e379b10 --- /dev/null +++ b/Source/Drivers/PS1080/PS1080.vcxproj.filters @@ -0,0 +1,823 @@ + + + + + {8e939bbb-560b-4742-b01a-ec3ef52df5d4} + + + {84e44c82-696a-46d1-a477-4c2cbec9304d} + + + {52710008-96c3-4eda-9b6e-30908776ba35} + + + {0e180fee-3143-4518-8461-dc4c4e6460c4} + + + {056c2231-a841-4ed6-bb9c-02a136a9a969} + + + {bd2edebe-84b9-468d-bfe0-3040945cfe57} + + + {84a4c5d6-dca7-41c2-8465-5c0d981decde} + + + {29abbe34-fc58-482d-b562-3c6d32a23e59} + + + {fbc0f7fb-da0b-4f35-90e9-27de346080c2} + + + {0fdc9505-6a0f-40b9-8338-735940e77bdd} + + + {6e7b92d6-07ae-4177-9e4a-1bb50add983c} + + + + + Core + + + Core + + + Core + + + Core + + + Core + + + Formats + + + Formats + + + Formats + + + Formats + + + Formats + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + Sensor + + + Sensor + + + Sensor + + + Sensor + + + Sensor + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + DriverImpl + + + DriverImpl + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Algorithms + + + Sensor\Algorithms + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Data Processors + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Algorithms + + + Sensor + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + + + Core + + + Core + + + Formats + + + Formats + + + Formats + + + Formats + + + Formats + + + Formats + + + Formats + + + Formats + + + Formats + + + Formats + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + DDK + + + Sensor + + + Sensor + + + Sensor + + + Sensor + + + Sensor + + + Sensor + + + Sensor + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + DriverImpl + + + DriverImpl + + + DDK + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Algorithms + + + Sensor\Algorithms + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Firmware + + + Sensor\Streams + + + Sensor\Data Processors + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Streams + + + Sensor\Algorithms + + + DriverImpl + + + Sensor + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + Sensor\Data Processors + + + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + Formats\LibJPEG + + + + + Resources + + + \ No newline at end of file diff --git a/Source/Drivers/PS1080/PS1080Console/Makefile b/Source/Drivers/PS1080/PS1080Console/Makefile new file mode 100644 index 0000000..4737d91 --- /dev/null +++ b/Source/Drivers/PS1080/PS1080Console/Makefile @@ -0,0 +1,32 @@ +include ../../../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../../../Bin + +INC_DIRS = \ + ../../../../Include \ + ../../../../ThirdParty/PSCommon/XnLib/Include \ + ../Include + +SRC_FILES = \ + *.cpp \ + +LIB_DIRS = ../../../../ThirdParty/PSCommon/XnLib/Bin/$(PLATFORM)-$(CFG) +USED_LIBS = XnLib OpenNI2 dl pthread + +ifeq ("$(OSTYPE)","Darwin") + INC_DIRS += /opt/local/include + LIB_DIRS += /opt/local/lib + LDFLAGS += -framework CoreFoundation -framework IOKit +endif + +ifneq ("$(OSTYPE)","Darwin") + USED_LIBS += rt usb-1.0 udev +else + USED_LIBS += usb-1.0.0 +endif + +CFLAGS += -Wall + +EXE_NAME = PS1080Console + +include ../../../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Source/Drivers/PS1080/PS1080Console/PS1080Console.cpp b/Source/Drivers/PS1080/PS1080Console/PS1080Console.cpp new file mode 100644 index 0000000..9929110 --- /dev/null +++ b/Source/Drivers/PS1080/PS1080Console/PS1080Console.cpp @@ -0,0 +1,1772 @@ +/***************************************************************************** +* * +* PrimeSense Sensor 5.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of PrimeSense Sensor * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +typedef bool (*cbfunc)(openni::Device& Device, vector& Command); + +map cbs; +map mnemonics; +map helps; + +XnVersions g_DeviceVersion; +const char* g_openMode; +openni::VideoStream g_depthStream; +openni::VideoStream g_colorStream; + +const XnUInt16 REQUIRE_MODE_PS = 0x0001; +const XnUInt16 REQUIRE_MODE_MAINTENANCE = 0x0002; +const XnUInt16 REQUIRE_MODE_ANY = 0xFFFF; + +#define SWITCH_MODE_WAIT 3000 + +bool atoi2(const char* str, int* pOut) +{ + int output = 0; + int base = 10; + int start = 0; + + if (strlen(str) > 1 && str[0] == '0' && str[1] == 'x') + { + start = 2; + base = 16; + } + + for (size_t i = start; i < strlen(str); i++) + { + output *= base; + if (str[i] >= '0' && str[i] <= '9') + output += str[i]-'0'; + else if (base == 16 && str[i] >= 'a' && str[i] <= 'f') + output += 10+str[i]-'a'; + else if (base == 16 && str[i] >= 'A' && str[i] <= 'F') + output += 10+str[i]-'A'; + else + return false; + } + *pOut = output; + return true; +} + + +void mainloop(openni::Device& Device, istream& istr, bool prompt) +{ + char buf[256]; + string str; + + vector Command; + + while (istr.good()) + { + if (prompt) + cout << "> "; + Command.clear(); + istr.getline(buf, 256); + str = buf; + size_t previous = 0, next = 0; + + while (1) + { + next = str.find(' ', previous); + + if (next != previous && previous != str.size()) + Command.push_back(str.substr(previous, next-previous)); + + if (next == str.npos) + break; + + previous = next+1; + } + + if (Command.size() > 0) + { + if (Command[0][0] == ';') + continue; + + for (unsigned int i = 0; i < Command[0].size(); i++) + Command[0][i] = (char)tolower(Command[0][i]); + + if (cbs.find(Command[0]) != cbs.end()) + { + if (!(*cbs[Command[0]])(Device, Command)) + return; + } + else if (mnemonics.find(Command[0]) != mnemonics.end()) + { + if (!(*mnemonics[Command[0]])(Device, Command)) + return; + } + else + { + cout << "Unknown command \"" << Command[0] << "\"" << endl; + } + } + } +} + +bool byebye(openni::Device& /*Device*/, vector& /*Command*/) +{ + cout << "Bye bye" << endl; + return false; +} + +bool Version(openni::Device& Device, vector& /*Command*/) +{ + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_VERSION, &g_DeviceVersion); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + XnChar strPlatformString[XN_DEVICE_MAX_STRING_LENGTH]; + int size = sizeof(strPlatformString); + rc = Device.getProperty(XN_MODULE_PROPERTY_SENSOR_PLATFORM_STRING, strPlatformString, &size); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + printf("Firmware: V%d.%d.%d", g_DeviceVersion.nMajor, g_DeviceVersion.nMinor, g_DeviceVersion.nBuild); + + if (strPlatformString[0] != '\0') + { + printf("-%s", strPlatformString); + } + + printf("; SDK: V%d.%d.%d.%d; Chip: 0x%08x\nFPGA: 0x%x; System: 0x%x; ", + g_DeviceVersion.SDK.nMajor, g_DeviceVersion.SDK.nMinor, g_DeviceVersion.SDK.nMaintenance, g_DeviceVersion.SDK.nBuild, + g_DeviceVersion.nChip, g_DeviceVersion.nFPGA, g_DeviceVersion.nSystemVersion); + + printf ("FWBaseLine: "); + if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_0_17) + { + printf ("V0.17"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_1_1) + { + printf ("V1.1"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_1_2) + { + printf ("V1.2"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_3_0) + { + printf ("V3.0"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_4_0) + { + printf ("V4.0"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_0) + { + printf ("V5.0"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_1) + { + printf ("V5.1"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_2) + { + printf ("V5.2"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_3) + { + printf ("V5.3"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_4) + { + printf ("V5.4"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_5) + { + printf ("V5.5"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_6) + { + printf ("V5.6"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_5_7) + { + printf ("V5.7"); + } + else if (g_DeviceVersion.FWVer == XN_SENSOR_FW_VER_UNKNOWN) + { + printf ("Unknown"); + } + + printf ("; Board: "); + if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_CDB_10) + { + printf ("CDB1.0"); + } + else if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_FPDB_10) + { + printf ("FPDB1.0"); + } + else if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_RD_3) + { + printf ("RD3.0"); + } + else if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_RD_5) + { + printf ("RD5.0"); + } + else if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_RD1081) + { + printf ("RD1081"); + } + else if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_RD1082) + { + printf ("RD1082"); + } + else if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_RD109) + { + printf ("RD109"); + } + else if (g_DeviceVersion.HWVer == XN_SENSOR_HW_VER_UNKNOWN) + { + printf ("Unknown"); + } + + printf ("; ChipType: "); + if (g_DeviceVersion.ChipVer == XN_SENSOR_CHIP_VER_PS1000) + { + printf ("PS1000"); + } + else if (g_DeviceVersion.ChipVer == XN_SENSOR_CHIP_VER_PS1080) + { + printf ("PS1080"); + } + else if (g_DeviceVersion.ChipVer == XN_SENSOR_CHIP_VER_PS1080A6) + { + printf ("PS1080A6"); + } + else if (g_DeviceVersion.ChipVer == XN_SENSOR_CHIP_VER_UNKNOWN) + { + printf ("Unknown"); + } + + printf ("\n"); + + return true; +} + +bool DeleteFile(openni::Device& Device, vector& Command) +{ + if (Command.size() != 2) + { + printf("Usage: %s \n", Command[0].c_str()); + return true; + } + + int nId; + if (!atoi2(Command[1].c_str(), &nId)) + { + printf("Id (%s) isn't a number\n", Command[1].c_str()); + return true; + } + printf("Deleting file id %d: ", nId); + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_DELETE_FILE, (XnUInt64)nId); + if (rc != openni::STATUS_OK) + { + printf("Failed: %s\n", openni::OpenNI::getExtendedError()); + } + + printf("Done\n"); + + return true; +} + +bool Attrib(openni::Device& Device, vector& Command) +{ + if (Command.size() < 2) + { + printf("Usage: %s [<+-> ...]\n", Command[0].c_str()); + printf("\t+r - Set read only. -r - Set not read only\n"); + return true; + } + int nId; + if (!atoi2(Command[1].c_str(), &nId)) + { + printf("Id (%s) isn't a number\n", Command[1].c_str()); + return true; + } + + XnFlashFile Files[100]; + + XnFlashFileList FileList; + FileList.pFiles = (XnFlashFile*)Files; + FileList.nFiles = 100; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_FILE_LIST, &FileList); + if (rc != openni::STATUS_OK) + { + printf("Couldn't get file list: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + XnFlashFile* pEntry = NULL; + + for (int i = 0; i < FileList.nFiles; i++) + { + if (FileList.pFiles[i].nId == nId) + pEntry = &FileList.pFiles[i]; + } + if (pEntry == NULL) + { + printf("No such id %d\n", nId); + return true; + } + + if (Command.size() == 2) + { + // Get + printf("0x%04x: ", pEntry->nAttributes); + + // All attributes + printf("%cr ", (pEntry->nAttributes & XnFileAttributeReadOnly) ? '+':'-'); + // Done + printf("\n"); + return true; + } + + XnUInt16 nNewAttributes = pEntry->nAttributes; + + for (unsigned int i = 2; i < Command.size(); i++) + { + if (Command[i] == "+r") + nNewAttributes |= XnFileAttributeReadOnly; + else if (Command[i] == "-r") + nNewAttributes &= ~XnFileAttributeReadOnly; + else + { + printf("Unknown attribute: %s\n", Command[i].c_str()); + return true; + } + } + + if (nNewAttributes == pEntry->nAttributes) + { + printf("Target attributes is same as existing one!\n"); + return true; + } + + XnFileAttributes Attribs; + Attribs.nId = pEntry->nId; + Attribs.nAttribs = nNewAttributes; + + rc = Device.setProperty(XN_MODULE_PROPERTY_FILE_ATTRIBUTES, Attribs); + if (rc != openni::STATUS_OK) + { + printf("Couldn't change attributes: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + return true; +} + +bool Connect(openni::Device& Device) +{ + openni::Status rc = openni::STATUS_OK; + + int nCounter = 10; + while (nCounter-- != 0) + { + rc = Device._openEx(NULL, g_openMode); + if (rc == openni::STATUS_OK || rc == openni::STATUS_NO_DEVICE) + break; + + // wait and try again + xnOSSleep(1000); + } + + if (rc != openni::STATUS_OK) + { + printf("Open failed: %s\n", openni::OpenNI::getExtendedError()); + return false; + } + + return true; +} + +bool Reconnect(openni::Device& Device, vector& /*Command*/) +{ + Device.close(); + + if (!Connect(Device)) + { + return false; + } + + printf("Reconnected\n"); + + return true; +} + +bool Sleep(openni::Device& /*Device*/, vector& Command) +{ + if (Command.size() != 2) + { + printf("Usage: %s \n", Command[0].c_str()); + return true; + } + + XnInt32 nMilliSeconds; + if (!atoi2(Command[1].c_str(), &nMilliSeconds)) + { + printf("%s doesn't describe a time quantity in milliseconds\n", Command[1].c_str()); + return true; + } + + xnOSSleep(nMilliSeconds); + return true; +} + +#define NUM_OF_FILE_TYPES 31 +bool FileList(openni::Device& Device, vector& /*Command*/) +{ + const char *FileTypes[NUM_OF_FILE_TYPES]= { "FILE_TABLE", + "SCRATCH_FILE", + "BOOT_SECTOR", + "BOOT_MANAGER", + "CODE_DOWNLOADER", + "MONITOR", + "APPLICATION", + "FIXED_PARAMS", + "DESCRIPTORS", + "DEFAULT_PARAMS", + "IMAGE_CMOS", + "DEPTH_CMOS", + "ALGORITHM_PARAMS", + "QVGA_REFERENCE", + "VGA_REFERENCE", + "MAINTENANCE", + "DEBUG_PARAMS", + "PRIME_PROCESSOR", + "GAIN_CONTROL", + "REG_PARAMS", + "ID_PARAMS", + "TEC_PARAMS", + "APC_PARAMS", + "SAFETY_PARAMS", + "PRODUCTION_FILE", + "UPGRADE_IN_PROGRESS", + "WAVELENGTH_CORRECTION", + "GMC_REF_OFFSET", + "NESA_PARAMS", + "SENSOR_FAULT", + "VENDOR_DATA", + }; + + XnFlashFile Files[100]; + + + XnFlashFileList FileList; + FileList.pFiles = (XnFlashFile*)Files; + FileList.nFiles = 100; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_FILE_LIST, &FileList); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + printf("%-3s %-22s %-8s %-8s %-8s %-8s\n", "Id", "Type", "Version", "Offset", "SizeInWords", "Crc"); + for (int i = 0; i < FileList.nFiles; i++) + { + XnFlashFile *Entry = &FileList.pFiles[i]; + const char *TypeCaption = "Unknown Type"; + if ( Entry->nType < NUM_OF_FILE_TYPES ) + TypeCaption = FileTypes[Entry->nType]; + printf("%-3d %-22s %2x.%02x.%04x 0x%-8X %-8d 0x%-8X", Entry->nId, TypeCaption, Entry->nVersion>>24, (Entry->nVersion>>16)&0xff, Entry->nVersion&0xffff, Entry->nOffset, Entry->nSize, Entry->nCrc); + if (Entry->nAttributes & 0x8000) + { + printf(" [R]"); + } + printf("\n"); + } + + return true; +} + +bool Reset(openni::Device& Device, vector& Command) +{ + if (Command.size() != 2) + { + printf("Usage: %s \n", Command[0].c_str()); + printf(" power|soft\n"); + return true; + } + + XnParamResetType Type; + if (Command[1] == "power") + Type = XN_RESET_TYPE_POWER; + else if (Command[1] == "soft") + Type = XN_RESET_TYPE_SOFT; + else + { + printf("Unknown reset type (power|soft)\n"); + return true; + } + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_RESET, (XnUInt64)Type); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + } + + return true; +} + +bool Log(openni::Device& Device, vector& /*Command*/) +{ + XnUChar LogBuffer[XN_MAX_LOG_SIZE] = {0}; + XnBool bAll = true; + openni::Status rc; + + do + { + LogBuffer[0] = '\0'; + int size = sizeof(LogBuffer); + rc = Device.getProperty(XN_MODULE_PROPERTY_FIRMWARE_LOG, LogBuffer, &size); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + break; + } + + if (LogBuffer[0] != '\0') + { + printf("%s", LogBuffer); + } + else + { + bAll = false; + } + } while (bAll); + + return true; +} + +bool Script(openni::Device& Device, vector& Command) +{ + + if (Command.size() != 2) + { + cout << "Usage: " << Command[0] << " " << endl; + return true; + } + string filename = ""; + + for (unsigned int i = 1; i < Command.size(); i++) + { + if (i != 1) + filename += " "; + filename += Command[i]; + } + + ifstream ifs; + ifs.open(filename.c_str()); + if (!ifs.good()) + { + cout << "Bad file" << endl; + return true; + } + mainloop(Device, ifs, false); + ifs.close(); + return true; +} + +bool Help(openni::Device& /*Device*/, vector& /*Command*/) +{ + for (map::iterator iter = cbs.begin(); iter != cbs.end(); ++iter) + { + cout << "\"" << iter->first << "\" - " << helps[iter->first] << endl; + } + + return true; +} + +bool SetGeneralParam(openni::Device& Device, vector& Command) +{ + if (Command.size() != 3) + { + cout << "Usage: " << Command[0] << " " << endl; + return true; + } + + int nParam, nValue; + + if (!atoi2(Command[1].c_str(), &nParam)) + { + printf("Don't understand %s as a parameter\n", Command[1].c_str()); + return true; + } + if (!atoi2(Command[2].c_str(), &nValue)) + { + printf("Don't understand %s as a value\n", Command[2].c_str()); + return true; + } + + XnInnerParamData Param; + Param.nParam = (unsigned short)nParam; + Param.nValue = (unsigned short)nValue; + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_FIRMWARE_PARAM, Param); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + else + { + printf("Done\n"); + } + + return true; +} + +bool GetGeneralParam(openni::Device& Device, vector& Command) +{ + if (Command.size() != 2) + { + cout << "Usage: " << Command[0] << " " << endl; + return true; + } + + int nParam; + if (!atoi2(Command[1].c_str(), &nParam)) + { + printf("Don't understand %s as a parameter\n", Command[1].c_str()); + return true; + } + + XnInnerParamData Param; + Param.nParam = (unsigned short)nParam; + Param.nValue = (unsigned short)0; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_FIRMWARE_PARAM, &Param); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + else + { + cout << "Param[" << Param.nParam << "] = " << Param.nValue << endl; + } + + return true; +} + +bool WriteI2C(openni::Device& Device, vector& Command, XnControlProcessingData& I2C) +{ + if (Command.size() != 5) + { + cout << "Usage: " << Command[0] << " " << Command[1] << " " << endl; + return true; + } + + int nRegister, nValue; + if (!atoi2(Command[3].c_str(), &nRegister)) + { + printf("Don't understand %s as a register\n", Command[3].c_str()); + return true; + } + if (!atoi2(Command[4].c_str(), &nValue)) + { + printf("Don't understand %s as a value\n", Command[4].c_str()); + return true; + } + I2C.nRegister = (unsigned short)nRegister; + I2C.nValue = (unsigned short)nValue; + + int nParam = 0; + + int command; + if (!atoi2(Command[2].c_str(), &command)) + { + printf("cmos should be 0 (depth) or 1 (image)\n"); + return true; + } + + if (command == 1) + nParam = XN_MODULE_PROPERTY_DEPTH_CONTROL; + else if (command == 0) + nParam = XN_MODULE_PROPERTY_IMAGE_CONTROL; + else + { + cout << "cmos must be 0/1" << endl; + return true; + } + + openni::Status rc = Device.setProperty(nParam, I2C); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + + return true; +} + +bool ReadI2C(openni::Device& Device, vector& Command, XnControlProcessingData& I2C) +{ + if (Command.size() != 4) + { + cout << "Usage: " << Command[0] << " " << Command[1] << " " << endl; + return true; + } + + int nRegister; + if (!atoi2(Command[3].c_str(), &nRegister)) + { + printf("Don't understand %s as a register\n", Command[3].c_str()); + return true; + } + I2C.nRegister = (unsigned short)nRegister; + + int nParam = 0; + + int command; + if (!atoi2(Command[2].c_str(), &command)) + { + cout << "cmos must be 0/1" << endl; + return true; + } + + if (command == 1) + nParam = XN_MODULE_PROPERTY_DEPTH_CONTROL; + else if (command == 0) + nParam = XN_MODULE_PROPERTY_IMAGE_CONTROL; + else + { + cout << "cmos must be 0/1" << endl; + return true; + } + + if (Device.getProperty(nParam, &I2C) != openni::STATUS_OK) + { + cout << "GetParam failed!" << endl; + return true; + } + + cout << "I2C(" << command << ")[0x" << hex << I2C.nRegister << "] = 0x" << hex << I2C.nValue << endl; + + return true; +} + +bool GeneralI2C(openni::Device& Device, vector& Command) +{ + if (Command.size() < 2) + { + cout << "Usage: " << Command[0] << " ..." << endl; + return true; + } + XnControlProcessingData I2C; + + if (Command[1] == "read") + { + return ReadI2C(Device, Command, I2C); + } + else if (Command[1] == "write") + { + return WriteI2C(Device, Command, I2C); + } + + cout << "Usage: " << Command[0] << " ..." << endl; + return true; +} + +bool WriteAHB(openni::Device& Device, vector& Command, XnAHBData& AHB) +{ + if (Command.size() != 5) + { + cout << "Usage: " << Command[0] << " " << Command[1] << " " << endl; + return true; + } + + int nRegister, nValue, nMask; + + if (!atoi2(Command[2].c_str(), &nRegister)) + { + printf("Can't understand %s as register\n", Command[2].c_str()); + return true; + } + if (!atoi2(Command[3].c_str(), &nValue)) + { + printf("Can't understand %s as value\n", Command[3].c_str()); + return true; + } + if (!atoi2(Command[4].c_str(), &nMask)) + { + printf("Can't understand %s as mask\n", Command[4].c_str()); + return true; + } + + + AHB.nRegister = nRegister; + AHB.nValue = nValue; + AHB.nMask = nMask; + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_AHB, AHB); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + + return true; +} + +bool ReadAHB(openni::Device& Device, vector& Command, XnAHBData& AHB) +{ + if (Command.size() != 3) + { + cout << "Usage: " << Command[0] << " " << Command[1] << " " << endl; + return true; + } + + int nRegister; + if (!atoi2(Command[2].c_str(), &nRegister)) + { + printf("Can't understand %s as register\n", Command[2].c_str()); + return true; + } + + AHB.nRegister = nRegister; + AHB.nValue = 0; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_AHB, &AHB); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + else + { + cout << "AHB[0x" << hex << AHB.nRegister << "] = 0x" << hex << AHB.nValue << endl; + } + + return true; +} + +bool GeneralAHB(openni::Device& Device, vector& Command) +{ + if (Command.size() < 2) + { + cout << "Usage: " << Command[0] << " ..." << endl; + return true; + } + XnAHBData AHB; + + if (Command[1] == "read") + { + return ReadAHB(Device, Command, AHB); + } + else if (Command[1] == "write") + { + return WriteAHB(Device, Command, AHB); + } + + cout << "Usage: " << Command[0] << " ..." << endl; + return true; +} + +bool Upload(openni::Device& Device, vector& Command) +{ + if (Command.size() < 3) + { + cout << "Usage: " << Command[0] << " [ro]" << endl; + return true; + } + int nOffset; + + if (!atoi2(Command[1].c_str(), &nOffset)) + { + printf("Can't understand %s as offset\n", Command[1].c_str()); + return true; + } + + XnUInt16 nAttributes = 0; + + if (Command.size() >= 4) + { + if (Command[3] == "ro") + { + nAttributes |= XnFileAttributeReadOnly; + } + } + + XnParamFileData ParamFile; + ParamFile.nOffset = nOffset; + ParamFile.strFileName = Command[2].c_str(); + ParamFile.nAttributes = nAttributes; + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_FILE, ParamFile); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + + return true; +} + +bool Download(openni::Device& Device, vector& Command) +{ + if (Command.size() < 3) + { + cout << "Usage: " << Command[0] << " " << endl; + return true; + } + int nId; + + if (!atoi2(Command[1].c_str(), &nId)) + { + printf("Can't understand %s as id\n", Command[1].c_str()); + return true; + } + XnParamFileData ParamFile = { (uint32_t) nId, Command[2].c_str()}; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_FILE, &ParamFile); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + + return true; +} + +bool Filter(openni::Device& Device, vector& Command) +{ + if (Command.size() < 2) + { + cout << "Usage: " << Command[0] << " ..." << endl; + return true; + } + + XnInt32 nFilter; + openni::Status rc; + + if (Command[1] == "get") + { + XnUInt64 nValue; + rc = Device.getProperty(XN_MODULE_PROPERTY_FIRMWARE_LOG_FILTER, &nValue); + if (rc == openni::STATUS_OK) + { + nFilter = (XnInt32)nValue; + printf("Filter 0x%04x\n", nFilter); + } + else + { + printf("Failed: %s\n", openni::OpenNI::getExtendedError()); + } + } + else if (Command[1] == "set") + { + if (Command.size() < 3) + { + cout << "Usage: " << Command[0] << " set " << endl; + return true; + } + if (!atoi2(Command[2].c_str(), &nFilter)) + { + printf("Can't understand %s as filter\n", Command[2].c_str()); + return true; + } + rc = Device.setProperty(XN_MODULE_PROPERTY_FIRMWARE_LOG_FILTER, (XnUInt64)nFilter); + if(rc == openni::STATUS_OK) + { + printf("Done.\n"); + } + else + { + printf("Failed: %s\n", openni::OpenNI::getExtendedError()); + } + } + else + { + cout << "Usage: " << Command[0] << " ..." << endl; + } + + return true; + +} + +bool Led(openni::Device& Device, vector& Command) +{ + if (Command.size() < 3) + { + cout << "Usage: " << Command[0] << " " << endl; + return true; + } + + int nLedId; + if (!atoi2(Command[1].c_str(), &nLedId)) + { + printf("Can't understand '%s' as LED id\n", Command[1].c_str()); + return true; + } + + int nState; + if (Command[2] == "on") + nState = 1; + else if (Command[2] == "off") + nState = 0; + else + { + printf("State must be 'on' or 'off'!\n"); + return true; + } + + XnLedState ledState; + ledState.nLedID = (uint16_t)nLedId; + ledState.nState = (uint16_t)nState; + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_LED_STATE, ledState); + if(rc == openni::STATUS_OK) + { + printf("Done.\n"); + } + else + { + printf("Failed: %s\n", openni::OpenNI::getExtendedError()); + } + return true; +} + +bool ReadFixed(openni::Device& Device, vector& Command) +{ + int nParam = -1; + if (Command.size() > 1) + { + if (!atoi2(Command[1].c_str(), &nParam)) + { + printf("Don't understand %s as a parameter\n", Command[1].c_str()); + return true; + } + } + + XnUInt32 anFixedParams[100]; + XnDynamicSizeBuffer buffer; + buffer.nMaxSize = sizeof(anFixedParams); + buffer.pData = anFixedParams; + buffer.nDataSize = 0; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_FIXED_PARAMS, &buffer); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + else + { + if (nParam != -1) + { + if (buffer.nDataSize < nParam * sizeof(XnUInt32)) + { + cout << "Invalid index! Last index is " << buffer.nDataSize / sizeof(XnUInt32) - 1 << endl; + } + else + { + cout << "Fixed Param [" << nParam << "] = " << anFixedParams[nParam] << endl; + } + } + else + { + for (XnUInt32 i = 0; i < buffer.nDataSize / sizeof(XnUInt32); ++i) + { + cout << "Fixed Param [" << i << "] = " << anFixedParams[i] << endl; + } + } + } + + return true; +} + +bool Debug(openni::Device& /*Device*/, vector& Command) +{ + for (unsigned int i = 0; i < Command.size(); i++) + cout << i << ". \"" << Command[i] << "\"" << endl; + + return true; +} + +void RegisterCB(string cmd, cbfunc func, const string& strHelp) +{ + for (unsigned int i = 0; i < cmd.size(); i++) + cmd[i] = (char)tolower(cmd[i]); + cbs[cmd] = func; + helps[cmd] = strHelp; +} + +void RegisterMnemonic(string strMnemonic, string strCommand) +{ + for (unsigned int i = 0; i < strCommand.size(); i++) + strCommand[i] = (char)tolower(strCommand[i]); + for (unsigned int i = 0; i < strMnemonic.size(); i++) + strMnemonic[i] = (char)tolower(strMnemonic[i]); + + if (cbs.find(strCommand) != cbs.end()) + { + mnemonics[strMnemonic] = cbs[strCommand]; + } +} + +bool ReadFlash(openni::Device& Device, vector& Command) +{ + if (Command.size() < 4) + { + cout << "Usage: " << Command[0] << " " << endl; + return true; + } + + XnParamFlashData ParamFlash; + + if (!atoi2(Command[1].c_str(), (int*)&ParamFlash.nOffset)) + { + printf("Can't understand %s as offset\n", Command[1].c_str()); + return true; + } + + if (!atoi2(Command[2].c_str(), (int*)&ParamFlash.nSize)) + { + printf("Can't understand %s as size\n", Command[1].c_str()); + return true; + } + + int nSizeInBytes = ParamFlash.nSize * sizeof(XnUInt16); + ParamFlash.pData = new XnUChar[nSizeInBytes]; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_FLASH_CHUNK, &ParamFlash); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + + xnOSSaveFile(Command[3].c_str(), ParamFlash.pData, nSizeInBytes); + + return true; +} + +bool RunBIST(openni::Device& Device, vector& Command) +{ + string sBistTests[] = + { + "ImageCmos", + "IrCmos", + "Potentiometer", + "Flash", + "FlashFull", + "Projector", + "Tec", + "NESA", + "NESAUnlimited" + }; + + int nBistTests = sizeof(sBistTests) / sizeof(string); + + string sBistErrors[] = + { + "Ram", + "Ir Cmos Control Bus", + "Ir Cmos Data Bus", + "Ir Cmos Bad Version", + "Ir Cmos Reset", + "Ir Cmos Trigger", + "Ir Cmos Strobe", + "Color Cmos Control Bus", + "Color Cmos Data Bus", + "Color Cmos Bad Version", + "Color Cmos Reset", + "Flash Write Line", + "Flash Test", + "Potentiometer Control Bus", + "Potentiometer", + "Audio Test", + "Projector LD Failure", + "Projector LD Failsafe Trigger", + "Projector Failsafe High Path", + "Projector Failsafe Low Path", + "Heater Crossed", + "Heater Disconnected", + "Cooler Crossed", + "Cooler Disconnected", + "TEC still initializing", + "TEC out of range", + "NESA", + }; + + int nBistErrors = sizeof(sBistErrors) / sizeof(string); + + XnBist bist; + bist.nTestsMask = 0; + + bool bShowUsage = false; + + if (Command.size() == 1) + { + bist.nTestsMask = (uint32_t)XN_BIST_ALL; + } + else if (Command.size() == 2 && Command[1] == "help") + { + bShowUsage = true; + } + else + { + for (XnUInt32 i = 1; i < Command.size(); ++i) + { + bool bFound = false; + + // search for this test + for (int j = 0; j < nBistTests; ++j) + { + if (sBistTests[j] == Command[i]) + { + bist.nTestsMask |= (1 << j); + bFound = true; + break; + } + } + + if (!bFound) + { + if (Command[i] == "all") + { + bist.nTestsMask = (uint32_t)XN_BIST_ALL; + } + else + { + cout << "Unknown test: " << Command[i] << endl; + bShowUsage = true; + break; + } + } + } + } + + if (bShowUsage) + { + cout << "Usage: " << Command[0] << " [test_list]" << endl; + cout << "where test_list can be one or more of:" << endl; + for (int i = 0; i < nBistTests; ++i) + { + cout << "\t" << sBistTests[i] << endl; + } + return true; + } + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_BIST, bist); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + if (bist.nFailures == 0) + { + cout << "BIST passed." << endl; + } + else + { + cout << "BIST failed. Returning code: " << bist.nFailures << endl; + cout << "The following tests have failed:" << endl; + for (int i = 0; i < nBistErrors; ++i) + { + int nMask = 1 << i; + if ((bist.nFailures & nMask) != 0) + { + cout << "\t" << sBistErrors[i] << endl; + } + } + } + + return true; +} + +bool CalibrateTec(openni::Device& Device, vector& Command) +{ + if (Command.size() < 3) + { + cout << "Usage: " << Command[0] << " " << Command[1] << " " << endl; + return true; + } + + int nSetPoint; + + if (!atoi2(Command[2].c_str(), (int*)&nSetPoint)) + { + printf("Can't understand %s as set point\n", Command[2].c_str()); + return true; + } + + if (nSetPoint > 0xFFFF) + { + printf("Set point can't fit in a 16-bit integer!\n"); + return true; + } + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_TEC_SET_POINT, (XnUInt64)nSetPoint); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + + return true; +} + +bool GetTecData(openni::Device& Device, vector& /*Command*/) +{ + XnTecData TecData; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_TEC_STATUS, &TecData); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + else + { + printf("SetPointVoltage: %hd\nCompensationVoltage: %hd\nDutyCycle: %hd\nHeatMode: %hd\nProportionalError: %d\nIntegralError: %d\nDeriativeError: %d\nScanMode: %hd\n", + TecData.m_SetPointVoltage, TecData.m_CompensationVoltage, TecData.m_TecDutyCycle, + TecData.m_HeatMode, TecData.m_ProportionalError, TecData.m_IntegralError, + TecData.m_DerivativeError, TecData.m_ScanMode); + } + + return true; +} + +bool GetTecFastConvergenceData(openni::Device& Device, vector& /*Command*/) +{ + XnTecFastConvergenceData TecData; + XnFloat SetPointTemperature; + XnFloat MeasuredTemperature; + XnFloat ErrorTemperature; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_TEC_FAST_CONVERGENCE_STATUS, &TecData); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + else + { + SetPointTemperature = TecData.m_SetPointTemperature; + MeasuredTemperature = TecData.m_MeasuredTemperature; + /* calculate error in temperature */ + ErrorTemperature = + (XnFloat)TecData.m_SetPointTemperature - (XnFloat)TecData.m_MeasuredTemperature; + + /* scale back temperature values, as they are given scaled by factor + of 100 (for precision) */ + SetPointTemperature = SetPointTemperature / 100; + MeasuredTemperature = MeasuredTemperature / 100; + ErrorTemperature = ErrorTemperature / 100; + + printf("SetPointTemperature: %f\nMeasuredTemperature: %f\nError: %f\nDutyCyclePercents: %hd\nHeatMode: %hd\nProportionalError: %d\nIntegralError: %d\nDeriativeError: %d\nScanMode: %hd\nTemperatureRange: %hd\n", + SetPointTemperature, MeasuredTemperature, ErrorTemperature, TecData.m_TecDutyCycle, + TecData.m_HeatMode, TecData.m_ProportionalError, TecData.m_IntegralError, + TecData.m_DerivativeError, TecData.m_ScanMode, TecData.m_TemperatureRange); + } + + return true; +} + +bool Tec(openni::Device& Device, vector& Command) +{ + if (Command.size() > 1) + { + if (Command[1] == "calib") + { + return CalibrateTec(Device, Command); + } + else if (Command[1] == "get") + { + return GetTecData(Device, Command); + } + } + + cout << "Usage: " << Command[0] << " ..." << endl; + return true; +} + +bool TecFC(openni::Device& Device, vector& Command) +{ + if (Command.size() > 1) + { + if (Command[1] == "calib") + { + return CalibrateTec(Device, Command); + } + else if (Command[1] == "get") + { + return GetTecFastConvergenceData(Device, Command); + } + } + + cout << "Usage: " << Command[0] << " ..." << endl; + return true; +} + + +bool CalibrateEmitter(openni::Device& Device, vector& Command) +{ + if (Command.size() < 3) + { + cout << "Usage: " << Command[0] << " " << Command[1] << " " << endl; + return true; + } + + int nSetPoint; + + if (!atoi2(Command[2].c_str(), (int*)&nSetPoint)) + { + printf("Can't understand %s as set point\n", Command[2].c_str()); + return true; + } + + if (nSetPoint > 0xFFFF) + { + printf("Set point can't fit in a 16-bit integer!\n"); + return true; + } + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_EMITTER_SET_POINT, (XnUInt64)nSetPoint); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + + return true; +} + +bool GetEmitterData(openni::Device& Device, vector& /*Command*/) +{ + XnEmitterData EmitterData; + + openni::Status rc = Device.getProperty(XN_MODULE_PROPERTY_EMITTER_STATUS, &EmitterData); + if (rc != openni::STATUS_OK) + { + printf("%s\n", openni::OpenNI::getExtendedError()); + } + else + { + printf("State: %hd\n", EmitterData.m_State); + printf("SetPointVoltage: %hd\n", EmitterData.m_SetPointVoltage); + printf("SetPointClocks: %hd\n", EmitterData.m_SetPointClocks); + printf("PD Reading: %hd\n", EmitterData.m_PD_Reading); + printf("EmitterSet: %hd\n", EmitterData.m_EmitterSet); + printf("SettingLogic: %hd\n", EmitterData.m_EmitterSettingLogic); + printf("LightMeasureLogic: %hd\n", EmitterData.m_LightMeasureLogic); + printf("APC Enabled: %hd\n", EmitterData.m_IsAPCEnabled); + printf("StepSize: %hd\n", EmitterData.m_EmitterSetStepSize); + + if (g_DeviceVersion.FWVer < XN_SENSOR_FW_VER_5_3) + { + printf("Tolerance: %hd\n", EmitterData.m_ApcTolerance); + } + else + { + printf("SubClocking: %hd\n", EmitterData.m_SubClocking); + printf("Precision: %hd\n", EmitterData.m_Precision); + } + } + + return true; +} + +bool Emitter(openni::Device& Device, vector& Command) +{ + if (Command.size() > 1) + { + if (Command[1] == "calib") + { + return CalibrateEmitter(Device, Command); + } + else if (Command[1] == "get") + { + return GetEmitterData(Device, Command); + } + else if (Command[1] == "on") + { + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_EMITTER_STATE, (XnUInt64)TRUE); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + } + return true; + } + else if (Command[1] == "off") + { + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_EMITTER_STATE, (XnUInt64)FALSE); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + } + return true; + } + } + + cout << "Usage: " << Command[0] << " ..." << endl; + return true; +} + +bool ProjectorFault(openni::Device& Device, vector& Command) +{ + if (Command.size() < 3) + { + cout << "Usage: " << Command[0] << " " << endl; + return true; + } + + int nMin, nMax; + if (!atoi2(Command[1].c_str(), (int*)&nMin)) + { + printf("Can't understand %s as min\n", Command[1].c_str()); + return true; + } + + if (!atoi2(Command[2].c_str(), (int*)&nMax)) + { + printf("Can't understand %s as max\n", Command[2].c_str()); + return true; + } + + XnProjectorFaultData ProjectorFaultData; + ProjectorFaultData.nMinThreshold = (uint16_t)nMin; + ProjectorFaultData.nMaxThreshold = (uint16_t)nMax; + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_PROJECTOR_FAULT, ProjectorFaultData); + if (rc != openni::STATUS_OK) + { + printf("Error: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + if (ProjectorFaultData.bProjectorFaultEvent) + printf("ProjectorFault event occurred!\n"); + else + printf("ProjectorFault OK.\n"); + + return true; +} + +bool StartReadData(openni::Device& Device, vector& Command) +{ + XnSensorUsbInterface usbInterface = XN_SENSOR_USB_INTERFACE_DEFAULT; + + if (Command.size() > 1) + { + if (xnOSStrCaseCmp(Command[1].c_str(), "bulk") == 0) + { + usbInterface = XN_SENSOR_USB_INTERFACE_BULK_ENDPOINTS; + } + else if (xnOSStrCaseCmp(Command[1].c_str(), "iso") == 0) + { + usbInterface = XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS; + } + else + { + cout << "Usage: " << Command[0] << " [bulk/iso]" << endl; + return true; + } + } + + openni::Status rc = Device.setProperty(XN_MODULE_PROPERTY_USB_INTERFACE, (XnUInt64)usbInterface); + if (rc != openni::STATUS_OK) + { + printf("Can't set USB interface: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + if (!g_depthStream.isValid()) + { + // create stream + rc = g_depthStream.create(Device, openni::SENSOR_DEPTH); + if (rc != openni::STATUS_OK) + { + printf("Can't create depth stream: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + } + + if (!g_colorStream.isValid()) + { + rc = g_depthStream.create(Device, openni::SENSOR_COLOR); + if (rc != openni::STATUS_OK) + { + printf("Can't create image stream: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + } + + // and cause reading to take place (without start streaming) + rc = g_depthStream.setProperty(XN_STREAM_PROPERTY_ACTUAL_READ_DATA, TRUE); + if (rc != openni::STATUS_OK) + { + printf("Can't start depth endpoint: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + rc = g_colorStream.setProperty(XN_STREAM_PROPERTY_ACTUAL_READ_DATA, TRUE); + if (rc != openni::STATUS_OK) + { + printf("Can't start image endpoint: %s\n", openni::OpenNI::getExtendedError()); + return true; + } + + printf("Endpoints are now open.\n"); + + return true; +} + +int main(int argc, char **argv) +{ + // Open Device. + openni::Status rc; + + // don't perform reset on startup + g_openMode = "R"; + + rc = openni::OpenNI::initialize(); + if (rc != openni::STATUS_OK) + { + printf("Init failed: %s\n", openni::OpenNI::getExtendedError()); + return 1; + } + + if (argc > 1 && strcmp(argv[1], "-safe") == 0) + { + g_openMode = "RL"; + --argc; + ++argv; + } + + openni::Device Device; + if (!Connect(Device)) + { + return 2; + } + + vector DummyCommand; + Version(Device, DummyCommand); + + RegisterCB("exit", &byebye, "Exit interactive mode"); + RegisterMnemonic("bye", "exit"); + + RegisterCB("set", &SetGeneralParam, "set inner parameters"); + RegisterCB("get", &GetGeneralParam, "get inner parameters"); + + RegisterCB("ahb", &GeneralAHB, "read/write the AHB"); + + RegisterCB("i2c", &GeneralI2C, "read/write the CMOS"); + + RegisterCB("upload", &Upload, "Upload file to flash"); + RegisterCB("download", &Download, "Download file from flash"); + + RegisterCB("help", &Help, "Get list of available commands"); + RegisterMnemonic("?", "help"); + + RegisterCB("script", &Script, "Run in batch mode"); + RegisterMnemonic("-s", "script"); + + RegisterCB("Version", &Version, "Get version"); + + RegisterCB("FileList", &FileList, "Get File List"); + RegisterMnemonic("dir", "FileList"); + + RegisterCB("Reset", &Reset, "Reset the device"); + + RegisterCB("Delete", &DeleteFile, "Delete File"); + + RegisterCB("Log", &Log, "Get Log"); + + RegisterCB("Filter", &Filter, "Set/Get Log Filter"); + + RegisterCB("Reconnect", &Reconnect, "Close+Open (Init+Shutdown if All)"); + + RegisterCB("Sleep", &Sleep, "Sleep some milliseconds. Useful in scripts"); + + RegisterCB("Attrib", &Attrib, "Get/Set file attributes"); + + RegisterCB("BIST", &RunBIST, "Run BIST"); + + RegisterCB("ReadFlash", &ReadFlash, "Reads a chunk of data from the flash"); + + RegisterCB("tec", &Tec, "Calibrate/Read TEC"); + + RegisterCB("tecfc", &TecFC, "Calibrate/Read TEC"); + + RegisterCB("emitter", &Emitter, "Calibrate/Read Emitter"); + + RegisterCB("projectorFault", &ProjectorFault, "Calibrate ProjectorFault"); + + RegisterCB("readfixed", &ReadFixed, "Read Fixed Params"); + + RegisterCB("led", &Led, "Set LED state"); + + RegisterCB("startread", &StartReadData, "Starts data reading from endpoints"); + + if (argc == 1) + { + mainloop(Device, cin, true); + } + else + { + vector Command; + for (int i = 1; i < argc; i++) + { + Command.push_back(argv[i]); + } + + for (unsigned int i = 0; i < Command[0].size(); i++) + Command[0][i] = (char)tolower(Command[0][i]); + + if (Command[0].size() == 0) + { + // + } + else if (cbs.find(Command[0]) != cbs.end()) + { + (*cbs[Command[0]])(Device, Command); + } + else if (mnemonics.find(Command[0]) != mnemonics.end()) + { + (*mnemonics[Command[0]])(Device, Command); + } + else + { + cout << "Unknown command \"" << Command[0] << "\"" << endl; + } + } + + g_depthStream.destroy(); + g_colorStream.destroy(); + Device.close(); + + return 0; +} diff --git a/Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj b/Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj new file mode 100644 index 0000000..b9adfea --- /dev/null +++ b/Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj @@ -0,0 +1,191 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D5709FB9-909D-415F-8F86-2F25BEF6CE23} + PS1080Console + Win32Proj + + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + MultiByte + true + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + false + + + + Disabled + ..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;..\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + EditAndContinue + 4250;4127;%(DisableSpecificWarnings) + true + true + + + XnLib.lib;OpenNI2.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + MachineX86 + true + + + + + X64 + + + Disabled + ..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;..\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + XnLib.lib;OpenNI2.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + MachineX64 + true + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + ..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;..\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + XnLib.lib;OpenNI2.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + true + true + MachineX86 + true + + + + + X64 + + + /MP %(AdditionalOptions) + MaxSpeed + true + ..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;..\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + XnLib.lib;OpenNI2.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + true + true + MachineX64 + true + + + + + + + + + \ No newline at end of file diff --git a/Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj.filters b/Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj.filters new file mode 100644 index 0000000..923e1bd --- /dev/null +++ b/Source/Drivers/PS1080/PS1080Console/PS1080Console.vcxproj.filters @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/Bayer.cpp b/Source/Drivers/PS1080/Sensor/Bayer.cpp new file mode 100644 index 0000000..a30cecd --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/Bayer.cpp @@ -0,0 +1,1158 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Many thanks to ROS guys for the improved algorithm! +//--------------------------------------------------------------------------- + +// Includes +#include "Bayer.h" + +#define AVG(a,b) (((int)(a) + (int)(b)) >> 1) +#define AVG3(a,b,c) (((int)(a) + (int)(b) + (int)(c)) / 3) +#define AVG4(a,b,c,d) (((int)(a) + (int)(b) + (int)(c) + (int)(d)) >> 2) +#define WAVG4(a,b,c,d,x,y) (unsigned char)( ( ((int)(a) + (int)(b)) * (int)(x) + ((int)(c) + (int)(d)) * (int)(y) ) / ( 2 * ((int)(x) + (int(y))) ) ) + +typedef enum +{ + Bilinear = 0, + EdgeAware, + EdgeAwareWeighted +} DebayeringMethod; + +void fillRGB(unsigned width, unsigned height, const XnUInt8* bayer_pixel, unsigned char* rgb_buffer, DebayeringMethod debayering_method, XnUInt32 nDownSampleStep) +{ + unsigned rgb_line_step = width * 3; +//--------------------------------------------------------------------------- +// Code + unsigned rgb_line_skip = rgb_line_step - width * 3; +//--------------------------------------------------------------------------- + if (nDownSampleStep == 1) + { + //register const XnUInt8 *bayer_pixel = image_md_->Data (); + register unsigned yIdx, xIdx; + + int bayer_line_step = width; + int bayer_line_step2 = width << 1; + + if (debayering_method == Bilinear) + { + // first two pixel values for first two lines + // Bayer 0 1 2 + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // 0 g R g + // line_step b g b + // line_step2 g r g + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = AVG( bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + + rgb_buffer += 6; + bayer_pixel += 2; + // rest of the first two lines + + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = bayer_pixel[bayer_line_step + 1]; + + // Bayer -1 0 1 2 + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = AVG( bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + + // main processing + + for (yIdx = 2; yIdx < height - 2; yIdx += 2) + { + // first two pixel values + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + // line_step2 g r g + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + // continue with rest of the line + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step], bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixels of the line + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + } + + //last two lines + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step B g b + //rgb_pixel[rgb_line_step ] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 1] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step b G b + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + // rest of the last two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[-bayer_line_step + 2]); + + rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[-1], bayer_pixel[1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + //rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // -1 g b g + // 0 r G r + // line_step g b g + rgb_buffer[rgb_line_step ] = rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[5] = rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 + // -1 g b g + // 0 r g R + // line_step g b g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[-bayer_line_step + 1]); + //rgb_pixel[5] = AVG( bayer_pixel[line_step], bayer_pixel[-line_step] ); + + // BGBG line + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g B g + //rgb_pixel[rgb_line_step ] = AVG2( bayer_pixel[-1], bayer_pixel[1] ); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g b G + //rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + } + else if (debayering_method == EdgeAware) + { + int dh, dv; + + // first two pixel values for first two lines + // Bayer 0 1 2 + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // 0 g R g + // line_step b g b + // line_step2 g r g + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = AVG( bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + + rgb_buffer += 6; + bayer_pixel += 2; + // rest of the first two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = bayer_pixel[bayer_line_step + 1]; + + // Bayer -1 0 1 2 + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = AVG( bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + // main processing + for (yIdx = 2; yIdx < height - 2; yIdx += 2) + { + // first two pixel values + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + // line_step2 g r g + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + // continue with rest of the line + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + + dh = abs (bayer_pixel[0] - bayer_pixel[2]); + dv = abs (bayer_pixel[-bayer_line_step + 1] - bayer_pixel[bayer_line_step + 1]); + + if (dh > dv) + rgb_buffer[4] = AVG (bayer_pixel[-bayer_line_step + 1], bayer_pixel[bayer_line_step + 1]); + else if (dv > dh) + rgb_buffer[4] = AVG (bayer_pixel[0], bayer_pixel[2]); + else + rgb_buffer[4] = AVG4 (bayer_pixel[-bayer_line_step + 1], bayer_pixel[bayer_line_step + 1], bayer_pixel[0], bayer_pixel[2]); + + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[5] = AVG4 (bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step], bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + dv = abs (bayer_pixel[0] - bayer_pixel[bayer_line_step2]); + dh = abs (bayer_pixel[bayer_line_step - 1] - bayer_pixel[bayer_line_step + 1]); + + if (dv > dh) + rgb_buffer[rgb_line_step + 1] = AVG (bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + else if (dh > dv) + rgb_buffer[rgb_line_step + 1] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step2]); + else + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixels of the line + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + } + + //last two lines + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step B g b + //rgb_pixel[rgb_line_step ] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 1] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step b G b + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + // rest of the last two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[-bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[-1], bayer_pixel[1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + //rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // -1 g b g + // 0 r G r + // line_step g b g + rgb_buffer[rgb_line_step ] = rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[5] = rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 + // -1 g b g + // 0 r g R + // line_step g b g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[-bayer_line_step + 1]); + //rgb_pixel[5] = AVG( bayer_pixel[line_step], bayer_pixel[-line_step] ); + + // BGBG line + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g B g + //rgb_pixel[rgb_line_step ] = AVG2( bayer_pixel[-1], bayer_pixel[1] ); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g b G + //rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + } + else if (debayering_method == EdgeAwareWeighted) + { + int dh, dv; + + // first two pixel values for first two lines + // Bayer 0 1 2 + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // 0 g R g + // line_step b g b + // line_step2 g r g + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = AVG( bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + + rgb_buffer += 6; + bayer_pixel += 2; + // rest of the first two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = bayer_pixel[bayer_line_step + 1]; + + // Bayer -1 0 1 2 + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = AVG( bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + // main processing + for (yIdx = 2; yIdx < height - 2; yIdx += 2) + { + // first two pixel values + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + // line_step2 g r g + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + // continue with rest of the line + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + + dh = abs (bayer_pixel[0] - bayer_pixel[2]); + dv = abs (bayer_pixel[-bayer_line_step + 1] - bayer_pixel[bayer_line_step + 1]); + + if (dv == 0 && dh == 0) + rgb_buffer[4] = AVG4 (bayer_pixel[1 - bayer_line_step], bayer_pixel[1 + bayer_line_step], bayer_pixel[0], bayer_pixel[2]); + else + rgb_buffer[4] = WAVG4 (bayer_pixel[1 - bayer_line_step], bayer_pixel[1 + bayer_line_step], bayer_pixel[0], bayer_pixel[2], dh, dv); + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[5] = AVG4 (bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step], bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + dv = abs (bayer_pixel[0] - bayer_pixel[bayer_line_step2]); + dh = abs (bayer_pixel[bayer_line_step - 1] - bayer_pixel[bayer_line_step + 1]); + + if (dv == 0 && dh == 0) + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + else + rgb_buffer[rgb_line_step + 1] = WAVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1], dh, dv); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixels of the line + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step ] = AVG4 (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = AVG4 (bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + //rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = AVG (bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + } + + //last two lines + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step ] = rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + //rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step B g b + //rgb_pixel[rgb_line_step ] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 1] = AVG (bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step b G b + //rgb_pixel[rgb_line_step + 3] = AVG( bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + // rest of the last two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) + { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG4 (bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = AVG4 (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], bayer_pixel[-bayer_line_step], bayer_pixel[-bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + rgb_buffer[rgb_line_step ] = AVG (bayer_pixel[-1], bayer_pixel[1]); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + //rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // -1 g b g + // 0 r G r + // line_step g b g + rgb_buffer[rgb_line_step ] = rgb_buffer[0] = AVG (bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[5] = rgb_buffer[2] = AVG (bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 + // -1 g b g + // 0 r g R + // line_step g b g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[-bayer_line_step + 1]); + //rgb_pixel[5] = AVG( bayer_pixel[line_step], bayer_pixel[-line_step] ); + + // BGBG line + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g B g + //rgb_pixel[rgb_line_step ] = AVG2( bayer_pixel[-1], bayer_pixel[1] ); + rgb_buffer[rgb_line_step + 1] = AVG3 (bayer_pixel[0], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g b G + //rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + //rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + } + //else + // THROW_OPENNI_EXCEPTION ("Unknwon debayering method: %d", (int)debayering_method); + } + //Warning: Downsampling mod is untested + else if (nDownSampleStep > 1) + { + // get each or each 2nd pixel group to find rgb values! + register unsigned bayerXStep = nDownSampleStep; + register unsigned bayerYSkip = (nDownSampleStep - 1) * width; + + // Downsampling and debayering at once + register const XnUInt8* bayer_buffer = bayer_pixel; + + for (register unsigned yIdx = 0; yIdx < height; ++yIdx, bayer_buffer += bayerYSkip, rgb_buffer += rgb_line_skip) // skip a line + { + for (register unsigned xIdx = 0; xIdx < width; ++xIdx, rgb_buffer += 3, bayer_buffer += bayerXStep) + { + rgb_buffer[ 2 ] = bayer_buffer[ width ]; + rgb_buffer[ 1 ] = AVG (bayer_buffer[0], bayer_buffer[ width + 1]); + rgb_buffer[ 0 ] = bayer_buffer[ 1 ]; + } + } + } +} + +void Bayer2RGB888(const XnUInt8* pBayerImage, XnUInt8* pRGBImage, XnUInt32 nXRes, XnUInt32 nYRes, XnUInt32 nDownSampleStep) +{ + fillRGB(nXRes, nYRes, pBayerImage, pRGBImage, DebayeringMethod(1), nDownSampleStep); // DebayeringMethod(0) == bilinear, (1) == edge aware, (2) == edge aware weighted +} + + + diff --git a/Source/Drivers/PS1080/Sensor/Bayer.h b/Source/Drivers/PS1080/Sensor/Bayer.h new file mode 100644 index 0000000..d0093e4 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/Bayer.h @@ -0,0 +1,42 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_BAYER_H_ +#define _XN_BAYER_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define BAYER_RED 0 +#define BAYER_GREEN 1 +#define BAYER_BLUE 2 +#define BAYER_BPP 3 + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +void Bayer2RGB888(const XnUInt8* pBayerImage, XnUInt8* pRGBImage, XnUInt32 nXRes, XnUInt32 nYRes, XnUInt32 nDownSampleStep); + +#endif //_XN_BAYER_H_ diff --git a/Source/Drivers/PS1080/Sensor/IXnSensorStream.h b/Source/Drivers/PS1080/Sensor/IXnSensorStream.h new file mode 100644 index 0000000..b12095e --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/IXnSensorStream.h @@ -0,0 +1,51 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __I_XN_SENSOR_STREAM_H__ +#define __I_XN_SENSOR_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Forward Declarations +//--------------------------------------------------------------------------- +class XnDataProcessor; + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class IXnSensorStream +{ +public: + virtual ~IXnSensorStream() {} + + virtual void GetFirmwareStreamConfig(XnResolutions* pnRes, XnUInt32* pnFPS) = 0; + virtual XnStatus ConfigureStreamImpl() = 0; + virtual XnStatus OpenStreamImpl() = 0; + virtual XnStatus CloseStreamImpl() = 0; + virtual XnStatus CreateDataProcessor(XnDataProcessor** ppProcessor) = 0; + virtual XnStatus MapPropertiesToFirmware() = 0; +}; + +#endif //__I_XN_SENSOR_STREAM_H__ diff --git a/Source/Drivers/PS1080/Sensor/Uncomp.cpp b/Source/Drivers/PS1080/Sensor/Uncomp.cpp new file mode 100644 index 0000000..cdcd1e4 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/Uncomp.cpp @@ -0,0 +1,313 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "Uncomp.h" +#include +#include + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +#define XN_CHECK_UNC_IMAGE_OUTPUT(x, y) \ + if (x > y) \ + { \ + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); \ + } + +#define XN_IMAGE_OUTPUT(pOutput, pOutputEnd, nValue) \ + XN_CHECK_UNC_IMAGE_OUTPUT(pOutput, pOutputEnd) \ + *pOutput = nValue; \ + ++pOutput; + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnStreamUncompressYUVImagePS(const XnUInt8* pInput, const XnUInt32 nInputSize, + XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nLineSize, + XnUInt32* pnActualRead, XnBool bLastPart) +{ + // Input is made of 4-bit elements. + const XnUInt8* pInputOrig = pInput; + const XnUInt8* pInputEnd = pInput + nInputSize; + XnUInt8* pOrigOutput = pOutput; + XnUInt8* pOutputEnd = pOutput + (*pnOutputSize); + XnUInt8 nLastFullValue[4] = {0}; + + // NOTE: we use variables of type uint32 instead of uint8 as an optimization (better CPU usage) + XnUInt32 nTempValue = 0; + XnUInt32 cInput = 0; + XnBool bReadByte = TRUE; + + if (nInputSize < sizeof(XnUInt8)) + { + printf("Buffer too small!\n"); + return (XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL); + } + + const XnUInt8* pInputLastPossibleStop = pInputOrig; + XnUInt8* pOutputLastPossibleStop = pOrigOutput; + + *pnActualRead = 0; + *pnOutputSize = 0; + + XnUInt32 nChannel = 0; + XnUInt32 nCurLineSize = 0; + + while (pInput < pInputEnd) + { + cInput = *pInput; + + if (bReadByte) + { + bReadByte = FALSE; + + if (cInput < 0xd0) // 0x0 to 0xc are diffs + { + // take high_element only + // diffs are between -6 and 6 (0x0 to 0xc) + nLastFullValue[nChannel] += XnInt8((cInput >> 4) - 6); + } + else if (cInput < 0xe0) // 0xd is dummy + { + // Do nothing + continue; + } + else // 0xe is not used, so this must be 0xf - full + { + // take two more elements + nTempValue = (cInput & 0x0f) << 4; + + if (++pInput == pInputEnd) + break; + + nTempValue += (*pInput >> 4); + nLastFullValue[nChannel] = (XnUInt8)nTempValue; + } + } + else + { + // take low-element + cInput &= 0x0f; + bReadByte = TRUE; + pInput++; + + if (cInput < 0xd) // 0x0 to 0xc are diffs + { + // diffs are between -6 and 6 (0x0 to 0xc) + nLastFullValue[nChannel] += (XnInt8)(cInput - 6); + } + else if (cInput < 0xe) // 0xd is dummy + { + // Do nothing + continue; + } + else // 0xe is not in use, so this must be 0xf - full + { + if (pInput == pInputEnd) + break; + + // take two more elements + nLastFullValue[nChannel] = *pInput; + pInput++; + } + } + + // write output + if (pOutput >= pOutputEnd) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + *pOutput = nLastFullValue[nChannel]; + pOutput++; + + nChannel++; + switch (nChannel) + { + case 2: + nLastFullValue[3] = nLastFullValue[1]; + break; + case 4: + nLastFullValue[1] = nLastFullValue[3]; + nChannel = 0; + break; + } + + nCurLineSize++; + if (nCurLineSize == nLineSize) + { + pInputLastPossibleStop = pInput; + pOutputLastPossibleStop = pOutput; + + nLastFullValue[0] = nLastFullValue[1] = nLastFullValue[2] = nLastFullValue[3] = 0; + nCurLineSize = 0; + } + } + + if (bLastPart == TRUE) + { + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput) * sizeof(XnUInt8); + *pnActualRead += (XnUInt32)(pInput - pInputOrig) * sizeof(XnUInt8); + } + else if ((pOutputLastPossibleStop != pOrigOutput) && (pInputLastPossibleStop != pInputOrig)) + { + *pnOutputSize = (XnUInt32)(pOutputLastPossibleStop - pOrigOutput) * sizeof(XnUInt8); + *pnActualRead += (XnUInt32)(pInputLastPossibleStop - pInputOrig) * sizeof(XnUInt8); + } + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnStreamUncompressImageNew(const XnUInt8* pInput, const XnUInt32 nInputSize, + XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nLineSize, + XnUInt32* pnActualRead, XnBool bLastPart) +{ + // Input is made of 4-bit elements. + const XnUInt8* pInputOrig = pInput; + const XnUInt8* pInputEnd = pInput + nInputSize; + XnUInt8* pOrigOutput = pOutput; + XnUInt8* pOutputEnd = pOutput + (*pnOutputSize); + XnUInt8 nLastFullValue[4] = {0}; + + // NOTE: we use variables of type uint32 instead of uint8 as an optimization (better CPU usage) + XnUInt32 nTempValue = 0; + XnUInt32 cInput = 0; + XnBool bReadByte = TRUE; + + if (nInputSize < sizeof(XnUInt8)) + { + printf("Buffer too small!\n"); + return (XN_STATUS_IO_COMPRESSED_BUFFER_TOO_SMALL); + } + + const XnUInt8* pInputLastPossibleStop = pInputOrig; + XnUInt8* pOutputLastPossibleStop = pOrigOutput; + + *pnActualRead = 0; + *pnOutputSize = 0; + + XnUInt32 nChannel = 0; + XnUInt32 nCurLineSize = 0; + + while (pInput < pInputEnd) + { + cInput = *pInput; + + if (bReadByte) + { + bReadByte = FALSE; + + if (cInput < 0xd0) // 0x0 to 0xc are diffs + { + // take high_element only + // diffs are between -6 and 6 (0x0 to 0xc) + nLastFullValue[nChannel] += (XnInt8)((cInput >> 4) - 6); + } + else if (cInput < 0xe0) // 0xd is dummy + { + // Do nothing + continue; + } + else // 0xe is not used, so this must be 0xf - full + { + // take two more elements + nTempValue = (cInput & 0x0f) << 4; + + if (++pInput == pInputEnd) + break; + + nTempValue += (*pInput >> 4); + nLastFullValue[nChannel] = (XnUInt8)nTempValue; + } + } + else + { + // take low-element + cInput &= 0x0f; + bReadByte = TRUE; + pInput++; + + if (cInput < 0xd) // 0x0 to 0xc are diffs + { + // diffs are between -6 and 6 (0x0 to 0xc) + nLastFullValue[nChannel] += (XnInt8)(cInput - 6); + } + else if (cInput < 0xe) // 0xd is dummy + { + // Do nothing + continue; + } + else // 0xe is not in use, so this must be 0xf - full + { + if (pInput == pInputEnd) + break; + + // take two more elements + nLastFullValue[nChannel] = *pInput; + pInput++; + } + } + + // write output + if (pOutput >= pOutputEnd) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + *pOutput = nLastFullValue[nChannel]; + pOutput++; + + nChannel++; + switch (nChannel) + { + case 2: + nChannel = 0; + break; + } + + nCurLineSize++; + if (nCurLineSize == nLineSize) + { + pInputLastPossibleStop = pInput; + pOutputLastPossibleStop = pOutput; + + nLastFullValue[0] = nLastFullValue[1] = nLastFullValue[2] = nLastFullValue[3] = 0; + nCurLineSize = 0; + } + } + + if (bLastPart == TRUE) + { + *pnOutputSize = (XnUInt32)(pOutput - pOrigOutput) * sizeof(XnUInt8); + *pnActualRead += (XnUInt32)(pInput - pInputOrig) * sizeof(XnUInt8); + } + else if ((pOutputLastPossibleStop != pOrigOutput) && (pInputLastPossibleStop != pInputOrig)) + { + *pnOutputSize = (XnUInt32)(pOutputLastPossibleStop - pOrigOutput) * sizeof(XnUInt8); + *pnActualRead += (XnUInt32)(pInputLastPossibleStop - pInputOrig) * sizeof(XnUInt8); + } + + // All is good... + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/Sensor/Uncomp.h b/Source/Drivers/PS1080/Sensor/Uncomp.h new file mode 100644 index 0000000..85837cd --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/Uncomp.h @@ -0,0 +1,43 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_UNCOMP_H_ +#define _XN_UNCOMP_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +XnStatus XnStreamUncompressImageNew(const XnUInt8* pInput, const XnUInt32 nInputSize, + XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nLineSize, + XnUInt32* pnActualRead, XnBool bLastPart); +XnStatus XnStreamUncompressYUVImagePS(const XnUInt8* pInput, const XnUInt32 nInputSize, + XnUInt8* pOutput, XnUInt32* pnOutputSize, XnUInt16 nLineSize, + XnUInt32* pnActualRead, XnBool bLastPart); + +#endif //_XN_UNCOMP_H_ diff --git a/Source/Drivers/PS1080/Sensor/XnAudioProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnAudioProcessor.cpp new file mode 100644 index 0000000..bc319c3 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnAudioProcessor.cpp @@ -0,0 +1,143 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#if 0 // Audio support + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnAudioProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnAudioProcessor::XnAudioProcessor(XnSensorAudioStream* pStream, XnSensorStreamHelper* pHelper, XnDeviceAudioBuffer* pBuffer, XnUInt32 nInputPacketSize) : + XnWholePacketProcessor(pHelper->GetPrivateData(), pStream->GetType(), nInputPacketSize), + m_AudioInDump(NULL), + m_pStream(pStream), + m_pBuffer(pBuffer), + m_pHelper(pHelper) +{ + m_AudioInDump = xnDumpFileOpen(XN_DUMP_AUDIO_IN, "AudioIn.pcm"); +} + +XnAudioProcessor::~XnAudioProcessor() +{ + xnDumpFileClose(m_AudioInDump); + GetStream()->NumberOfChannelsProperty().OnChangeEvent().Unregister(m_hNumChannelsCallback); +} + +XnStatus XnAudioProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnWholePacketProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetStream()->NumberOfChannelsProperty().OnChangeEvent().Register(DeleteChannelChangedCallback, this, m_hNumChannelsCallback); + XN_IS_STATUS_OK(nRetVal); + + CalcDeleteChannel(); + + return (XN_STATUS_OK); +} + +void XnAudioProcessor::ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData) +{ + xnOSEnterCriticalSection(&m_pBuffer->hLock); + + // take write packet + XnUChar* pWritePacket = m_pBuffer->pAudioBuffer + (m_pBuffer->nAudioWriteIndex * m_pBuffer->nAudioPacketSize); + + if (m_bDeleteChannel) + { + XnUInt16* pSamples = (XnUInt16*)pData; + XnUInt16* pSamplesEnd = (XnUInt16*)(pData + pHeader->nBufSize); + XnUInt16* pOutput = (XnUInt16*)pWritePacket; + + while (pSamples < pSamplesEnd) + { + *pOutput = *pSamples; + + pOutput++; + // skip a sample + pSamples += 2; + } + } + else + { + // copy data + xnOSMemCopy(pWritePacket, pData, pHeader->nBufSize); + } + + // mark timestamp + if (ShouldUseHostTimestamps()) + { + m_pBuffer->pAudioPacketsTimestamps[m_pBuffer->nAudioWriteIndex] = GetHostTimestamp(); + } + else + { + m_pBuffer->pAudioPacketsTimestamps[m_pBuffer->nAudioWriteIndex] = CreateTimestampFromDevice(pHeader->nTimeStamp); + } + + if (m_nLastPacketID % 10 == 0) + { + XnUInt64 nSysTime; + xnOSGetTimeStamp(&nSysTime); + + xnDumpFileWriteString(m_pDevicePrivateData->BandwidthDump, "%llu,%s,%d,%d\n", + nSysTime, "Audio", -1, m_nBytesReceived); + + m_nBytesReceived = 0; + } + + // move write index forward + m_pBuffer->nAudioWriteIndex = (m_pBuffer->nAudioWriteIndex + 1) % m_pBuffer->nAudioBufferNumOfPackets; + + // if write index got to read index (end of buffer), move read index forward (and loose a packet) + if (m_pBuffer->nAudioWriteIndex == m_pBuffer->nAudioReadIndex) + { + m_pBuffer->nAudioReadIndex = (m_pBuffer->nAudioReadIndex + 1) % m_pBuffer->nAudioBufferNumOfPackets; + } + + xnOSLeaveCriticalSection(&m_pBuffer->hLock); + + xnDumpFileWriteBuffer(m_AudioInDump, pData, pHeader->nBufSize); + + if (m_pBuffer->pAudioCallback != NULL) + { + m_pBuffer->pAudioCallback(m_pBuffer->pAudioCallbackCookie); + } +} + +void XnAudioProcessor::CalcDeleteChannel() +{ + m_bDeleteChannel = (m_pHelper->GetFirmwareVersion() >= XN_SENSOR_FW_VER_5_2 && GetStream()->GetNumberOfChannels() == 1); +} + +XnStatus XnAudioProcessor::DeleteChannelChangedCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnAudioProcessor* pThis = (XnAudioProcessor*)pCookie; + pThis->CalcDeleteChannel(); + return XN_STATUS_OK; +} + +#endif // Audio support \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnAudioProcessor.h b/Source/Drivers/PS1080/Sensor/XnAudioProcessor.h new file mode 100644 index 0000000..e639a30 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnAudioProcessor.h @@ -0,0 +1,73 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_AUDIO_PROCESSOR_H__ +#define __XN_AUDIO_PROCESSOR_H__ + +#if 0 // Audio support + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnWholePacketProcessor.h" +#include "XnSensorAudioStream.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnAudioProcessor : public XnWholePacketProcessor +{ +public: + XnAudioProcessor(XnSensorAudioStream* pStream, XnSensorStreamHelper* pHelper, XnDeviceAudioBuffer* pBuffer, XnUInt32 nInputPacketSize); + ~XnAudioProcessor(); + + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData); + + inline XnSensorAudioStream* GetStream() + { + return m_pStream; + } + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + void CalcDeleteChannel(); + static XnStatus XN_CALLBACK_TYPE DeleteChannelChangedCallback(const XnProperty* pSender, void* pCookie); + + /** Used to dump Audio In data. */ + XnDumpFile* m_AudioInDump; + XnBool m_bDeleteChannel; + XnSensorAudioStream* m_pStream; + XnDeviceAudioBuffer* m_pBuffer; + XnSensorStreamHelper* m_pHelper; + + XnCallbackHandle m_hNumChannelsCallback; +}; + +#endif // Audio support +#endif //__XN_AUDIO_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.cpp new file mode 100644 index 0000000..286354e --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.cpp @@ -0,0 +1,161 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnBayerImageProcessor.h" +#include "Uncomp.h" +#include "Bayer.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnBayerImageProcessor::XnBayerImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnBayerImageProcessor::~XnBayerImageProcessor() +{ +} + +XnStatus XnBayerImageProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnImageProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_ContinuousBuffer, GetExpectedOutputSize()); + + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_GRAY8: + break; + case ONI_PIXEL_FORMAT_RGB888: + XN_VALIDATE_BUFFER_ALLOCATE(m_UncompressedBayerBuffer, GetExpectedOutputSize()); + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_SENSOR_PROTOCOL_IMAGE, "Unsupported image output format: %d", GetStream()->GetOutputFormat()); + } + + return (XN_STATUS_OK); +} + +void XnBayerImageProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnBayerImageProcessor::ProcessFramePacketChunk") + + // if output format is Gray8, we can write directly to output buffer. otherwise, we need + // to write to a temp buffer. + XnBuffer* pWriteBuffer = (GetStream()->GetOutputFormat() == ONI_PIXEL_FORMAT_GRAY8) ? GetWriteBuffer() : &m_UncompressedBayerBuffer; + + const XnUChar* pBuf = NULL; + XnUInt32 nBufSize = 0; + + // check if we have bytes stored from previous calls + if (m_ContinuousBuffer.GetSize() > 0) + { + // we have no choice. We need to append current buffer to previous bytes + if (m_ContinuousBuffer.GetFreeSpaceInBuffer() < nDataSize) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_DEPTH, "Bad overflow image! %d", m_ContinuousBuffer.GetSize()); + FrameIsCorrupted(); + } + else + { + m_ContinuousBuffer.UnsafeWrite(pData, nDataSize); + } + + pBuf = m_ContinuousBuffer.GetData(); + nBufSize = m_ContinuousBuffer.GetSize(); + } + else + { + // we can process the data directly + pBuf = pData; + nBufSize = nDataSize; + } + + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + XnUInt32 nWrittenOutput = nOutputSize; + XnUInt32 nActualRead = 0; + XnBool bLastPart = pHeader->nType == XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_END && (nDataOffset + nDataSize) == pHeader->nBufSize; + XnStatus nRetVal = XnStreamUncompressImageNew(pBuf, nBufSize, pWriteBuffer->GetUnsafeWritePointer(), + &nWrittenOutput, (XnUInt16)GetActualXRes(), &nActualRead, bLastPart); + + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_IMAGE, "Image decompression failed: %s (%d of %d, requested %d, last %d)", xnGetStatusString(nRetVal), nWrittenOutput, nBufSize, nOutputSize, bLastPart); + FrameIsCorrupted(); + return; + } + + pWriteBuffer->UnsafeUpdateSize(nWrittenOutput); + + nBufSize -= nActualRead; + m_ContinuousBuffer.Reset(); + + // if we have any bytes left, keep them for next time + if (nBufSize > 0) + { + pBuf += nActualRead; + m_ContinuousBuffer.UnsafeWrite(pBuf, nBufSize); + } + + XN_PROFILING_END_SECTION +} + +void XnBayerImageProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnImageProcessor::OnStartOfFrame(pHeader); + m_ContinuousBuffer.Reset(); + m_UncompressedBayerBuffer.Reset(); +} + +void XnBayerImageProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XN_PROFILING_START_SECTION("XnBayerImageProcessor::OnEndOfFrame") + + // if data was written to temp buffer, convert it now + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_GRAY8: + break; + case ONI_PIXEL_FORMAT_RGB888: + { + Bayer2RGB888(m_UncompressedBayerBuffer.GetData(), GetWriteBuffer()->GetUnsafeWritePointer(), GetActualXRes(), GetActualYRes(), 1); + GetWriteBuffer()->UnsafeUpdateSize(GetActualXRes()*GetActualYRes()*3); + m_UncompressedBayerBuffer.Reset(); + } + break; + default: + assert(0); + return; + } + + XnImageProcessor::OnEndOfFrame(pHeader); + m_ContinuousBuffer.Reset(); + + XN_PROFILING_END_SECTION +} diff --git a/Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.h new file mode 100644 index 0000000..65f5f29 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnBayerImageProcessor.h @@ -0,0 +1,57 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_BAYER_IMAGE_PROCESSOR_H__ +#define __XN_BAYER_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnBayerImageProcessor : public XnImageProcessor +{ +public: + XnBayerImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnBayerImageProcessor(); + + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnBuffer m_ContinuousBuffer; + XnBuffer m_UncompressedBayerBuffer; +}; + +#endif //__XN_BAYER_IMAGE_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnCmosInfo.cpp b/Source/Drivers/PS1080/Sensor/XnCmosInfo.cpp new file mode 100644 index 0000000..426427f --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnCmosInfo.cpp @@ -0,0 +1,82 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnCmosInfo.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnCmosInfo::XnCmosInfo(XnSensorFirmware* pFirmware, XnDevicePrivateData* pDevicePrivateData) : + m_pFirmware(pFirmware), + m_pDevicePrivateData(pDevicePrivateData) +{ + xnOSMemSet(m_pCurrCmosBlankingInfo, 0, sizeof(m_pCurrCmosBlankingInfo)); +} + +XnCmosInfo::~XnCmosInfo() +{ +} + +XnStatus XnCmosInfo::SetCmosConfig(XnCMOSType nCmos, XnResolutions nResolution, XnUInt32 nFPS) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_pFirmware->GetInfo()->nFWVer >= XN_SENSOR_FW_VER_5_1) + { + // take blanking info + XnCmosBlankingInformation* pInfo = NULL; + + // search the list if we already have this info + for (XnCmosBlankingDataList::Iterator it = m_CmosBlankingInfo.Begin(); it != m_CmosBlankingInfo.End(); ++it) + { + XnCmosBlankingData& data = *it; + if (data.nRes == nResolution && data.nFPS == nFPS) + { + pInfo = &data.BlankingInfo; + break; + } + } + + if (pInfo == NULL) + { + // not found in list. fetch it from FW + XnCmosBlankingData data; + data.nRes = nResolution; + data.nFPS = nFPS; + + nRetVal = XnHostProtocolAlgorithmParams(m_pDevicePrivateData, XN_HOST_PROTOCOL_ALGORITHM_BLANKING, &data.BlankingInfo, sizeof(XnCmosBlankingInformation), nResolution, (XnUInt16)nFPS); + XN_IS_STATUS_OK(nRetVal); + + // add to list + nRetVal = m_CmosBlankingInfo.AddFirst(data); + XN_IS_STATUS_OK(nRetVal); + + // take its info (take a pointer to the object in the list, and not to the one on the stack) + pInfo = &m_CmosBlankingInfo.Begin()->BlankingInfo; + } + + m_pCurrCmosBlankingInfo[nCmos] = &pInfo->Coefficients[nCmos]; + } + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/Sensor/XnCmosInfo.h b/Source/Drivers/PS1080/Sensor/XnCmosInfo.h new file mode 100644 index 0000000..00ce090 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnCmosInfo.h @@ -0,0 +1,60 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_CMOS_INFO_H__ +#define __XN_CMOS_INFO_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnSensorFirmware.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef struct +{ + XnCmosBlankingInformation BlankingInfo; + XnResolutions nRes; + XnUInt32 nFPS; +} XnCmosBlankingData; + +class XnCmosInfo +{ +public: + XnCmosInfo(XnSensorFirmware* pFirmware, XnDevicePrivateData* pDevicePrivateData); + ~XnCmosInfo(); + + XnStatus SetCmosConfig(XnCMOSType nCmos, XnResolutions nResolution, XnUInt32 nFPS); + + inline const XnCmosBlankingCoefficients* GetBlankingCoefficients(XnCMOSType nCmos) const { return m_pCurrCmosBlankingInfo[nCmos]; } + +private: + typedef xnl::List XnCmosBlankingDataList; + + XnSensorFirmware* m_pFirmware; + XnDevicePrivateData* m_pDevicePrivateData; + + XnCmosBlankingDataList m_CmosBlankingInfo; + XnCmosBlankingCoefficients* m_pCurrCmosBlankingInfo[XN_CMOS_COUNT]; +}; + +#endif //__XN_CMOS_INFO_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnDataProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnDataProcessor.cpp new file mode 100644 index 0000000..df7b57c --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDataProcessor.cpp @@ -0,0 +1,237 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDataProcessor.h" +#include +#include "XnSensor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnDataProcessor::XnDataProcessor(XnDevicePrivateData* pDevicePrivateData, const XnChar* csName) : + m_pDevicePrivateData(pDevicePrivateData), + m_nBytesReceived(0), + m_nLastPacketID(0), + m_csName(csName), + m_bUseHostTimestamps(FALSE) +{ + m_TimeStampData.csStreamName = csName; + m_TimeStampData.bFirst = TRUE; + m_bUseHostTimestamps = pDevicePrivateData->pSensor->ShouldUseHostTimestamps(); +} + +XnDataProcessor::~XnDataProcessor() +{} + +XnStatus XnDataProcessor::Init() +{ + return (XN_STATUS_OK); +} + +void XnDataProcessor::ProcessData(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnDataProcessor::ProcessData") + + // count these bytes + m_nBytesReceived += nDataSize; + + // check if we start a new packet + if (nDataOffset == 0) + { + // make sure no packet was lost + if (pHeader->nPacketID != m_nLastPacketID+1 && pHeader->nPacketID != 0) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "%s: Expected %x, got %x", m_csName, m_nLastPacketID+1, pHeader->nPacketID); + OnPacketLost(); + } + + m_nLastPacketID = pHeader->nPacketID; + + // log packet arrival + XnUInt64 nNow; + xnOSGetHighResTimeStamp(&nNow); + xnDumpFileWriteString(m_pDevicePrivateData->MiniPacketsDump, "%llu,0x%hx,0x%hx,0x%hx,%u\n", nNow, pHeader->nType, pHeader->nPacketID, pHeader->nBufSize, pHeader->nTimeStamp); + } + + ProcessPacketChunk(pHeader, pData, nDataOffset, nDataSize); + + XN_PROFILING_END_SECTION +} + +void XnDataProcessor::OnPacketLost() +{} + +XnUInt64 XnDataProcessor::CreateTimestampFromDevice(XnUInt32 nDeviceTimeStamp) +{ + XnUInt64 nNow; + xnOSGetHighResTimeStamp(&nNow); + + // we register the first TS calculated as time-zero. Every stream's TS data will be + // synchronized with it + if (m_pDevicePrivateData->nGlobalReferenceTS == 0) + { + xnOSEnterCriticalSection(&m_pDevicePrivateData->hEndPointsCS); + if (m_pDevicePrivateData->nGlobalReferenceTS == 0) + { + m_pDevicePrivateData->nGlobalReferenceTS = nDeviceTimeStamp; + m_pDevicePrivateData->nGlobalReferenceOSTime = nNow; + } + xnOSLeaveCriticalSection(&m_pDevicePrivateData->hEndPointsCS); + } + + const XnUInt64 nWrapPoint = ((XnUInt64)XN_MAX_UINT32) + 1; + XnUInt64 nResultInTicks; + const XnUInt32 nDumpCommentMaxLength = 200; + XnChar csDumpComment[nDumpCommentMaxLength] = ""; + XnBool bCheckSanity = TRUE; + + if (m_TimeStampData.bFirst) + { + /* + This is a bit tricky, as we need to synchronize the first timestamp of different streams. + We somehow need to translate 32-bit tick counts to 64-bit timestamps. The device timestamps + wrap-around every ~71.5 seconds (for PS1080 @ 60 MHz). + Lets assume the first packet of the first stream got timestamp X. Now we get the first packet of another + stream with a timestamp Y. + We need to figure out what is the relation between X and Y. + We do that by analyzing the following scenarios: + 1. Y is after X, in the same period (no wraparound yet). + 2. Y is after X, in a different period (one or more wraparounds occurred). + 3. Y is before X, in the same period (might happen due to race condition). + 4. Y is before X, in a different period (this can happen if X is really small, and Y is almost at wraparound). + + The following code tried to handle all those cases. It uses an OS timer to try and figure out how + many wraparounds occurred. + */ + + // estimate the number of wraparound that occurred using OS time + XnUInt64 nOSTime = nNow - m_pDevicePrivateData->nGlobalReferenceOSTime; + + // calculate wraparound length + XnDouble fWrapAroundInMicroseconds = nWrapPoint / (XnDouble)m_pDevicePrivateData->fDeviceFrequency; + + // perform a rough estimation + XnInt32 nWraps = (XnInt32)(nOSTime / fWrapAroundInMicroseconds); + + // now fix the estimation by clipping TS to the correct wraparounds + XnInt64 nEstimatedTicks = + nWraps * nWrapPoint + // wraps time + nDeviceTimeStamp - m_pDevicePrivateData->nGlobalReferenceTS; + + XnInt64 nEstimatedTime = (XnInt64)(nEstimatedTicks / (XnDouble)m_pDevicePrivateData->fDeviceFrequency); + + if (nEstimatedTime < nOSTime - 0.5 * fWrapAroundInMicroseconds) + nWraps++; + else if (nEstimatedTime > nOSTime + 0.5 * fWrapAroundInMicroseconds) + nWraps--; + + // handle the two special cases - 3 & 4 in which we get a timestamp which is + // *before* global TS (meaning before time 0) + if (nWraps < 0 || // case 4 + (nWraps == 0 && nDeviceTimeStamp < m_pDevicePrivateData->nGlobalReferenceTS)) // case 3 + { + nDeviceTimeStamp = m_pDevicePrivateData->nGlobalReferenceTS; + nWraps = 0; + } + + m_TimeStampData.nReferenceTS = m_pDevicePrivateData->nGlobalReferenceTS; + m_TimeStampData.nTotalTicksAtReferenceTS = nWrapPoint * nWraps; + m_TimeStampData.nLastDeviceTS = 0; + m_TimeStampData.bFirst = FALSE; + nResultInTicks = 0; + bCheckSanity = FALSE; // no need. + sprintf(csDumpComment, "Init. Total Ticks in Ref TS: %llu", m_TimeStampData.nTotalTicksAtReferenceTS); + } + + if (nDeviceTimeStamp > m_TimeStampData.nLastDeviceTS) // this is the normal case + { + nResultInTicks = m_TimeStampData.nTotalTicksAtReferenceTS + nDeviceTimeStamp - m_TimeStampData.nReferenceTS; + } + else // wrap around occurred + { + // add the passed time to the reference time + m_TimeStampData.nTotalTicksAtReferenceTS += (nWrapPoint + nDeviceTimeStamp - m_TimeStampData.nReferenceTS); + // mark reference timestamp + m_TimeStampData.nReferenceTS = nDeviceTimeStamp; + + sprintf(csDumpComment, "Wrap around. Refernce TS: %u / TotalTicksAtReference: %llu", m_TimeStampData.nReferenceTS, m_TimeStampData.nTotalTicksAtReferenceTS); + + nResultInTicks = m_TimeStampData.nTotalTicksAtReferenceTS; + } + + m_TimeStampData.nLastDeviceTS = nDeviceTimeStamp; + + // calculate result in microseconds + // NOTE: Intel compiler does too much optimization, and we loose up to 5 milliseconds. We perform + // the entire calculation in XnDouble as a workaround + XnDouble dResultTimeMicroSeconds = (XnDouble)nResultInTicks / (XnDouble)m_pDevicePrivateData->fDeviceFrequency; + XnUInt64 nResultTimeMilliSeconds = (XnUInt64)(dResultTimeMicroSeconds / 1000.0); + + XnBool bIsSane = TRUE; + + // perform sanity check + if (bCheckSanity && (nResultTimeMilliSeconds > (m_TimeStampData.nLastResultTime + XN_SENSOR_TIMESTAMP_SANITY_DIFF*1000))) + { + bIsSane = FALSE; + xnOSStrAppend(csDumpComment, ",Didn't pass sanity. Will try to re-sync.", nDumpCommentMaxLength); + } + + XnUInt64 nResult = (XnUInt64)dResultTimeMicroSeconds; + + // dump it + xnDumpFileWriteString(m_pDevicePrivateData->TimestampsDump, "%llu,%s,%u,%llu,%s\n", nNow, m_TimeStampData.csStreamName, nDeviceTimeStamp, nResult, csDumpComment); + + if (bIsSane) + { + m_TimeStampData.nLastResultTime = nResultTimeMilliSeconds; + return (nResult); + } + else + { + // sanity failed. We lost sync. restart + m_TimeStampData.bFirst = TRUE; + return CreateTimestampFromDevice(nDeviceTimeStamp); + } +} + +XnUInt64 XnDataProcessor::GetHostTimestamp() +{ + XnUInt64 nNow; + xnOSGetHighResTimeStamp(&nNow); + + // we register the first TS calculated as time-zero. Every stream's TS data will be + // synchronized with it + if (m_pDevicePrivateData->nGlobalReferenceTS == 0) + { + xnOSEnterCriticalSection(&m_pDevicePrivateData->hEndPointsCS); + if (m_pDevicePrivateData->nGlobalReferenceTS == 0) + { + m_pDevicePrivateData->nGlobalReferenceTS = (XnUInt32)nNow; + m_pDevicePrivateData->nGlobalReferenceOSTime = nNow; + } + xnOSLeaveCriticalSection(&m_pDevicePrivateData->hEndPointsCS); + } + + XnUInt64 nResultTimeMicroseconds = nNow - m_pDevicePrivateData->nGlobalReferenceOSTime; + return nResultTimeMicroseconds; +} diff --git a/Source/Drivers/PS1080/Sensor/XnDataProcessor.h b/Source/Drivers/PS1080/Sensor/XnDataProcessor.h new file mode 100644 index 0000000..5aa45dd --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDataProcessor.h @@ -0,0 +1,102 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DATA_PROCESSOR_H__ +#define __XN_DATA_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" +#include "XnDeviceSensorProtocol.h" +#include + +/** +* Base class for all data processors. +*/ +class XnDataProcessor +{ +public: + XnDataProcessor(XnDevicePrivateData* pDevicePrivateData, const XnChar* csName); + virtual ~XnDataProcessor(); + +//--------------------------------------------------------------------------- +// Methods +//--------------------------------------------------------------------------- +public: + /** + * Initializes a Data Processor. + * + * @param pDevicePrivateData [in] A pointer to the device. + * @param csName [in] The name of the stream. + */ + virtual XnStatus Init(); + + /** + * Handles some data from this stream. + * + * @param pHeader [in] A pointer to current packet header. + * @param pData [in] A pointer to the data. + * @param nDataOffset [in] The offset of this data chunk inside current packet. + * @param nDataSize [in] Size of the data in bytes. + */ + void ProcessData(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + + inline XnBool ShouldUseHostTimestamps() { return m_bUseHostTimestamps; } + +//--------------------------------------------------------------------------- +// Virtual Functions +//--------------------------------------------------------------------------- +protected: + virtual void ProcessPacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) = 0; + virtual void OnPacketLost(); + +//--------------------------------------------------------------------------- +// Utility Functions +//--------------------------------------------------------------------------- +protected: + /* + * Gets a calculated timestamp from the device timestamp. + * + * @param nDeviceTimeStamp [in] The device TS to translate. + */ + virtual XnUInt64 CreateTimestampFromDevice(XnUInt32 nDeviceTimeStamp); + + XnUInt64 GetHostTimestamp(); + +//--------------------------------------------------------------------------- +// Class Members +//--------------------------------------------------------------------------- +protected: + XnDevicePrivateData* m_pDevicePrivateData; + /* The number of bytes received so far (since last time this member was reset). */ + XnUInt32 m_nBytesReceived; + /* Stores last packet ID */ + XnUInt16 m_nLastPacketID; + /* The name of the stream. */ + const XnChar* m_csName; + +private: + /* Data used for calculating timestamps. */ + XnTimeStampData m_TimeStampData; + XnBool m_bUseHostTimestamps; +}; + +#endif //__XN_DATA_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.cpp b/Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.cpp new file mode 100644 index 0000000..75ab0dd --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.cpp @@ -0,0 +1,93 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDataProcessorHolder.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnDataProcessorHolder::XnDataProcessorHolder() : + m_hLock(NULL), + m_pProcessor(NULL) +{ +} + +XnDataProcessorHolder::~XnDataProcessorHolder() +{ + xnOSCloseCriticalSection(&m_hLock); + XN_DELETE(m_pProcessor); +} + +XnStatus XnDataProcessorHolder::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSCreateCriticalSection(&m_hLock); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnDataProcessorHolder::Lock() +{ + xnOSEnterCriticalSection(&m_hLock); +} + +void XnDataProcessorHolder::Unlock() +{ + xnOSLeaveCriticalSection(&m_hLock); +} + +void XnDataProcessorHolder::Replace(XnDataProcessor* pNew) +{ + // lock first, to make sure processor is not in use right now + Lock(); + + if (m_pProcessor != NULL) + { + XN_DELETE(m_pProcessor); + } + + m_pProcessor = pNew; + + Unlock(); +} + +void XnDataProcessorHolder::ProcessData(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + if (m_pProcessor == NULL) + return; + + // lock first + Lock(); + + // check again (it could have been replaced while we waited to lock) + if (m_pProcessor != NULL) + { + m_pProcessor->ProcessData(pHeader, pData, nDataOffset, nDataSize); + } + + Unlock(); +} + diff --git a/Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.h b/Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.h new file mode 100644 index 0000000..06c4141 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDataProcessorHolder.h @@ -0,0 +1,53 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_STREAM_PROCESSOR_HOLDER_H__ +#define __XN_STREAM_PROCESSOR_HOLDER_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDataProcessor.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnDataProcessorHolder +{ +public: + XnDataProcessorHolder(); + ~XnDataProcessorHolder(); + + XnStatus Init(); + + void Replace(XnDataProcessor* pNew); + + void Lock(); + void Unlock(); + void ProcessData(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + +private: + XN_DISABLE_COPY_AND_ASSIGN(XnDataProcessorHolder); + + XN_CRITICAL_SECTION_HANDLE m_hLock; + XnDataProcessor* m_pProcessor; +}; + +#endif //__XN_STREAM_PROCESSOR_HOLDER_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnDepthProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnDepthProcessor.cpp new file mode 100644 index 0000000..de75ac0 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDepthProcessor.cpp @@ -0,0 +1,205 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDepthProcessor.h" +#include "XnSensor.h" +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnDepthProcessor::XnDepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnFrameStreamProcessor(pStream, pHelper, pBufferManager, XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_START, XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_END), + m_nPaddingPixelsOnEnd(0), + m_applyRegistrationOnEnd(FALSE), + m_nExpectedFrameSize(0), + m_bShiftToDepthAllocated(FALSE), + m_pShiftToDepthTable(pStream->GetShiftToDepthTable()) +{ +} + +XnDepthProcessor::~XnDepthProcessor() +{ + if (m_bShiftToDepthAllocated) + { + xnOSFree(m_pShiftToDepthTable); + } +} + +XnStatus XnDepthProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnFrameStreamProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_SHIFT_9_2: + { + // optimization. We create a LUT shift-to-shift. See comment up. + m_pShiftToDepthTable = (OniDepthPixel*)xnOSMalloc(sizeof(OniDepthPixel)*XN_DEVICE_SENSOR_MAX_SHIFT_VALUE); + XN_VALIDATE_ALLOC_PTR(m_pShiftToDepthTable); + for (XnUInt32 i = 0; i < XN_DEVICE_SENSOR_MAX_SHIFT_VALUE; ++i) + { + m_pShiftToDepthTable[i] = (OniDepthPixel)i; + } + m_bShiftToDepthAllocated = TRUE; + m_noDepthValue = 2047; + } + break; + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + case ONI_PIXEL_FORMAT_DEPTH_100_UM: + m_noDepthValue = 0; + break; + default: + XN_ASSERT(FALSE); + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_SENSOR_PROTOCOL_DEPTH, "Unknown Depth output: %d", GetStream()->GetOutputFormat()); + } + + return (XN_STATUS_OK); +} + +void XnDepthProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + // call base + XnFrameStreamProcessor::OnStartOfFrame(pHeader); + + m_nExpectedFrameSize = CalculateExpectedSize(); + + m_applyRegistrationOnEnd = ( + (GetStream()->GetOutputFormat() == ONI_PIXEL_FORMAT_DEPTH_1_MM || GetStream()->GetOutputFormat() == ONI_PIXEL_FORMAT_DEPTH_100_UM) && + GetStream()->m_DepthRegistration.GetValue() == TRUE && + GetStream()->m_FirmwareRegistration.GetValue() == FALSE); + + if (m_pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_1 && pHeader->nTimeStamp != 0) + { + // PATCH: starting with v5.1, the timestamp field of the SOF packet, is the number of pixels + // that should be prepended to the frame. + XnUInt32 nPaddingPixelsOnStart = pHeader->nTimeStamp >> 16; + m_nPaddingPixelsOnEnd = pHeader->nTimeStamp & 0x0000FFFF; + + PadPixels(nPaddingPixelsOnStart); + } +} + +XnUInt32 XnDepthProcessor::CalculateExpectedSize() +{ + XnUInt32 nExpectedDepthBufferSize = GetStream()->GetXRes() * GetStream()->GetYRes(); + + // when cropping is turned on, actual depth size is smaller + if (GetStream()->m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nExpectedDepthBufferSize = (XnUInt32)(GetStream()->m_FirmwareCropSizeX.GetValue() * GetStream()->m_FirmwareCropSizeY.GetValue()); + } + + nExpectedDepthBufferSize *= sizeof(OniDepthPixel); + + return nExpectedDepthBufferSize; +} + +void XnDepthProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + // pad pixels + if (m_nPaddingPixelsOnEnd != 0) + { + PadPixels(m_nPaddingPixelsOnEnd); + m_nPaddingPixelsOnEnd = 0 ; + } + + if (GetWriteBuffer()->GetSize() != GetExpectedSize()) + { + xnLogWarning(XN_MASK_SENSOR_READ, "Read: Depth buffer is corrupt. Size is %u (!= %u)", GetWriteBuffer()->GetSize(), GetExpectedSize()); + FrameIsCorrupted(); + } + else + { + if (m_applyRegistrationOnEnd) + { + GetStream()->ApplyRegistration((OniDepthPixel*)GetWriteBuffer()->GetData()); + } + } + + OniFrame* pFrame = GetWriteFrame(); + pFrame->sensorType = ONI_SENSOR_DEPTH; + + pFrame->videoMode.pixelFormat = GetStream()->GetOutputFormat(); + pFrame->videoMode.resolutionX = GetStream()->GetXRes(); + pFrame->videoMode.resolutionY = GetStream()->GetYRes(); + pFrame->videoMode.fps = GetStream()->GetFPS(); + + if (GetStream()->m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + pFrame->width = (int)GetStream()->m_FirmwareCropSizeX.GetValue(); + pFrame->height = (int)GetStream()->m_FirmwareCropSizeY.GetValue(); + pFrame->cropOriginX = (int)GetStream()->m_FirmwareCropOffsetX.GetValue(); + pFrame->cropOriginY = (int)GetStream()->m_FirmwareCropOffsetY.GetValue(); + pFrame->croppingEnabled = TRUE; + } + else + { + pFrame->width = pFrame->videoMode.resolutionX; + pFrame->height = pFrame->videoMode.resolutionY; + pFrame->cropOriginX = 0; + pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + } + + pFrame->stride = pFrame->width * GetStream()->GetBytesPerPixel(); + + // call base + XnFrameStreamProcessor::OnEndOfFrame(pHeader); +} + +void XnDepthProcessor::PadPixels(XnUInt32 nPixels) +{ + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + // check for overflow + if (!CheckWriteBufferForOverflow(nPixels * sizeof(OniDepthPixel))) + { + return; + } + + OniDepthPixel* pDepth = (OniDepthPixel*)GetWriteBuffer()->GetUnsafeWritePointer(); + + // place the no-depth value + for (XnUInt32 i = 0; i < nPixels; ++i, ++pDepth) + { + *pDepth = m_noDepthValue; + } + pWriteBuffer->UnsafeUpdateSize(nPixels * sizeof(OniDepthPixel)); +} + +void XnDepthProcessor::OnFrameReady(XnUInt32 nFrameID, XnUInt64 nFrameTS) +{ + XnFrameStreamProcessor::OnFrameReady(nFrameID, nFrameTS); + + m_pDevicePrivateData->pSensor->GetFPSCalculator()->MarkDepth(nFrameID, nFrameTS); +} diff --git a/Source/Drivers/PS1080/Sensor/XnDepthProcessor.h b/Source/Drivers/PS1080/Sensor/XnDepthProcessor.h new file mode 100644 index 0000000..1940633 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDepthProcessor.h @@ -0,0 +1,92 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEPTH_PROCESSOR_H__ +#define __XN_DEPTH_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFrameStreamProcessor.h" +#include "XnSensorDepthStream.h" + +//--------------------------------------------------------------------------- +// Compilation Checks +//--------------------------------------------------------------------------- + +// Optimization: in order to save branches in the code itself, we create a shift-to-depth +// map which will actually translate shift-to-shift. This optimization relies on the +// fact that both shifts and depths are 16-bit long. If this is not the case, +// this optimization should be re-written. +// Then, any processor can always go through this LUT, no matter what the output format is. +#if (OniDepthPixel != XnUInt16) + #error "Depth and Shift do not have the same size. Need to reconsider optimization!" +#endif + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +class XnDepthProcessor : public XnFrameStreamProcessor +{ +public: + XnDepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + virtual ~XnDepthProcessor(); + + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnFrameReady(XnUInt32 nFrameID, XnUInt64 nFrameTS); + + //--------------------------------------------------------------------------- + // Helper Functions + //--------------------------------------------------------------------------- + inline XnSensorDepthStream* GetStream() + { + return (XnSensorDepthStream*)XnFrameStreamProcessor::GetStream(); + } + + inline OniDepthPixel GetOutput(XnUInt16 nShift) + { + return m_pShiftToDepthTable[nShift]; + } + + inline XnUInt32 GetExpectedSize() + { + return m_nExpectedFrameSize; + } + +private: + void PadPixels(XnUInt32 nPixels); + XnUInt32 CalculateExpectedSize(); + + XnUInt32 m_nPaddingPixelsOnEnd; + XnBool m_applyRegistrationOnEnd; + XnUInt32 m_nExpectedFrameSize; + XnBool m_bShiftToDepthAllocated; + OniDepthPixel* m_pShiftToDepthTable; + OniDepthPixel m_noDepthValue; +}; + +#endif //__XN_DEPTH_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.cpp b/Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.cpp new file mode 100644 index 0000000..5cfda9e --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.cpp @@ -0,0 +1,199 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "XnDeviceEnumeration.h" +#include + +//--------------------------------------------------------------------------- +// Globals +//--------------------------------------------------------------------------- +XnBool XnDeviceEnumeration::ms_initialized = FALSE; +XnDeviceEnumeration::DeviceConnectivityEvent XnDeviceEnumeration::ms_connectedEvent; +XnDeviceEnumeration::DeviceConnectivityEvent XnDeviceEnumeration::ms_disconnectedEvent; +XnDeviceEnumeration::DevicesHash XnDeviceEnumeration::ms_devices; +xnl::Array XnDeviceEnumeration::ms_aRegistrationHandles; +XN_CRITICAL_SECTION_HANDLE XnDeviceEnumeration::ms_lock; + +XnDeviceEnumeration::XnUsbId XnDeviceEnumeration::ms_supportedProducts[] = +{ + { 0x1D27, 0x0500 }, + { 0x1D27, 0x0600 }, + { 0x1D27, 0x0601 }, + { 0x1D27, 0x0609 }, +}; + +XnUInt32 XnDeviceEnumeration::ms_supportedProductsCount = sizeof(XnDeviceEnumeration::ms_supportedProducts) / sizeof(XnDeviceEnumeration::ms_supportedProducts[0]); + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnDeviceEnumeration::Initialize() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (ms_initialized) + { + return XN_STATUS_OK; + } + + nRetVal = xnUSBInit(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = xnOSCreateCriticalSection(&ms_lock); + XN_IS_STATUS_OK(nRetVal); + + const XnUSBConnectionString* astrDevicePaths; + XnUInt32 nCount; + + // check all products + for (XnUInt32 i = 0; i < ms_supportedProductsCount; ++i) + { + // register for USB events + XnRegistrationHandle hRegistration = NULL; + nRetVal = xnUSBRegisterToConnectivityEvents(ms_supportedProducts[i].vendorID, ms_supportedProducts[i].productID, OnConnectivityEventCallback, &ms_supportedProducts[i], &hRegistration); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ms_aRegistrationHandles.AddLast(hRegistration); + XN_IS_STATUS_OK(nRetVal); + + // and enumerate for existing ones + nRetVal = xnUSBEnumerateDevices(ms_supportedProducts[i].vendorID, ms_supportedProducts[i].productID, &astrDevicePaths, &nCount); + XN_IS_STATUS_OK(nRetVal); + + for (XnUInt32 j = 0; j < nCount; ++j) + { + OnConnectivityEvent(astrDevicePaths[j], XN_USB_EVENT_DEVICE_CONNECT, ms_supportedProducts[i]); + } + + xnUSBFreeDevicesList(astrDevicePaths); + } + + ms_initialized = TRUE; + + return XN_STATUS_OK; +} + +void XnDeviceEnumeration::Shutdown() +{ + if (ms_initialized) + { + for (XnUInt32 i = 0; i < ms_aRegistrationHandles.GetSize(); ++i) + { + xnUSBUnregisterFromConnectivityEvents(ms_aRegistrationHandles[i]); + } + ms_aRegistrationHandles.Clear(); + ms_connectedEvent.Clear(); + ms_disconnectedEvent.Clear(); + + xnOSCloseCriticalSection(&ms_lock); + + xnUSBShutdown(); + + ms_devices.Clear(); + + ms_initialized = FALSE; + } +} + +void XnDeviceEnumeration::OnConnectivityEvent(const XnChar* uri, XnUSBEventType eventType, XnUsbId usbId) +{ + xnl::AutoCSLocker lock(ms_lock); + + if (eventType == XN_USB_EVENT_DEVICE_CONNECT) + { + if (ms_devices.Find(uri) == ms_devices.End()) + { + OniDeviceInfo deviceInfo; + deviceInfo.usbVendorId = usbId.vendorID; + deviceInfo.usbProductId = usbId.productID; + xnOSStrCopy(deviceInfo.uri, uri, sizeof(deviceInfo.uri)); + xnOSStrCopy(deviceInfo.vendor, "PrimeSense", sizeof(deviceInfo.vendor)); + xnOSStrCopy(deviceInfo.name, "PS1080", sizeof(deviceInfo.name)); + + // add it to hash + ms_devices.Set(uri, deviceInfo); + + // raise event + ms_connectedEvent.Raise(deviceInfo); + } + } + else if (eventType == XN_USB_EVENT_DEVICE_DISCONNECT) + { + OniDeviceInfo deviceInfo; + if (XN_STATUS_OK == ms_devices.Get(uri, deviceInfo)) + { + // raise event + ms_disconnectedEvent.Raise(deviceInfo); + + // remove it + ms_devices.Remove(uri); + } + } +} + +void XN_CALLBACK_TYPE XnDeviceEnumeration::OnConnectivityEventCallback(XnUSBEventArgs* pArgs, void* pCookie) +{ + XnUsbId usbId = *(XnUsbId*)pCookie; + OnConnectivityEvent(pArgs->strDevicePath, pArgs->eventType, usbId); +} + +XnStatus XnDeviceEnumeration::IsSensorLowBandwidth(const XnChar* uri, XnBool* pbIsLowBandwidth) +{ + *pbIsLowBandwidth = FALSE; + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + XnChar cpMatchString[XN_FILE_MAX_PATH]; + + // WAVI Detection: + // Normal USB string: \\?\usb#vid_1d27&pid_0600#6&XXXXXXXX&0&2 + // WAVI USB String: \\?\usb#vid_1d27&pid_0600#1&1d270600&2&3 + // ^^^^^^^^ - VID/PID is always repeated here with the WAVI. + // Regular USB devices will have the port/hub chain instead. + if ((xnOSStrCaseCmp(uri, "\\\\?\\usb#vid_") >= 0) && (xnOSStrLen(uri) > 25)) + { + strncpy(&cpMatchString[0], &uri[12], 4); //VID + strncpy(&cpMatchString[4], &uri[21], 4); //PID + cpMatchString[8] = 0; + + if (strstr ((char*)uri,cpMatchString) != 0) + { + *pbIsLowBandwidth = TRUE; + } + } +#endif + + return (XN_STATUS_OK); +} + +OniDeviceInfo* XnDeviceEnumeration::GetDeviceInfo(const XnChar* uri) +{ + OniDeviceInfo* pInfo = NULL; + xnl::AutoCSLocker lock(ms_lock); + + if (ms_devices.Get(uri, pInfo) == XN_STATUS_OK) + { + return pInfo; + } + else + { + return NULL; + } +} + diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.h b/Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.h new file mode 100644 index 0000000..bfc9902 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceEnumeration.h @@ -0,0 +1,69 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_DEVICE_ENUMERATION_H_ +#define _XN_DEVICE_ENUMERATION_H_ + +#include +#include +#include +#include +#include + +class XnDeviceEnumeration +{ +public: + typedef xnl::Event1Arg DeviceConnectivityEvent; + + static XnStatus Initialize(); + static void Shutdown(); + + static OniDeviceInfo* GetDeviceInfo(const XnChar* uri); + + static DeviceConnectivityEvent::Interface& ConnectedEvent() { return ms_connectedEvent; } + static DeviceConnectivityEvent::Interface& DisconnectedEvent() { return ms_disconnectedEvent; } + + static XnStatus EnumerateSensors(OniDeviceInfo* aDevices, XnUInt32* pnCount); + static XnStatus IsSensorLowBandwidth(const XnChar* connectionString, XnBool* pbIsLowband); + +private: + typedef struct XnUsbId + { + XnUInt16 vendorID; + XnUInt16 productID; + } XnUsbId; + + typedef xnl::StringsHash DevicesHash; + + static void XN_CALLBACK_TYPE OnConnectivityEventCallback(XnUSBEventArgs* pArgs, void* pCookie); + static void OnConnectivityEvent(const XnChar* uri, XnUSBEventType eventType, XnUsbId usbId); + + static XnBool ms_initialized; + static DeviceConnectivityEvent ms_connectedEvent; + static DeviceConnectivityEvent ms_disconnectedEvent; + + static XnUsbId ms_supportedProducts[]; + static XnUInt32 ms_supportedProductsCount; + static DevicesHash ms_devices; + static xnl::Array ms_aRegistrationHandles; + static XN_CRITICAL_SECTION_HANDLE ms_lock; +}; + +#endif \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceSensor.h b/Source/Drivers/PS1080/Sensor/XnDeviceSensor.h new file mode 100644 index 0000000..110f802 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceSensor.h @@ -0,0 +1,502 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_DEVICESENSOR_H_ +#define _XN_DEVICESENSOR_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include "XnDeviceSensorIO.h" +#include +#include +#include +#include +#include +#include "XnSensorFPS.h" +#include "XnFirmwareInfo.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_DEVICE_SENSOR_THREAD_KILL_TIMEOUT 5000 + +#define XN_DEVICE_SENSOR_DEPTH_CMOS_XRES 1280 +#define XN_DEVICE_SENSOR_DEPTH_CMOS_YRES 1024 + +#define XN_DEVICE_SENSOR_VGA_DEPTH_XRES 640 +#define XN_DEVICE_SENSOR_VGA_DEPTH_YRES 480 + +#define XN_DEVICE_SENSOR_MIN_DEPTH 0 +#define XN_DEVICE_SENSOR_MAX_DEPTH_1_MM 10000 +#define XN_DEVICE_SENSOR_MAX_DEPTH_100_UM 65534 +#define XN_DEVICE_SENSOR_NO_DEPTH_VALUE 0 +#define XN_DEVICE_SENSOR_MAX_SHIFT_VALUE 2048/*336*/ + +#define XN_DEVICE_SENSOR_BOARDID_SEP ":" +#define XN_DEVICE_SENSOR_DEFAULT_ID "*" + +#define XN_DEVICE_SENSOR_INI_FILE_EXT ".ini" + +#define XN_SENSOR_PROTOCOL_SENSOR_ID_LENGTH 16 + +#define XN_SENSOR_TIMESTAMP_SANITY_DIFF 10 // in ms + +#define XN_MASK_DEVICE_SENSOR "DeviceSensor" +#define XN_MASK_DEVICE_IO "DeviceIO" +#define XN_MASK_SENSOR_PROTOCOL "DeviceSensorProtocol" +#define XN_MASK_SENSOR_PROTOCOL_IMAGE XN_MASK_SENSOR_PROTOCOL "Image" +#define XN_MASK_SENSOR_PROTOCOL_DEPTH XN_MASK_SENSOR_PROTOCOL "Depth" +#define XN_MASK_SENSOR_PROTOCOL_AUDIO XN_MASK_SENSOR_PROTOCOL "Audio" +#define XN_MASK_SENSOR_READ "DeviceSensorRead" +#define XN_MASK_SENSOR_READ_IMAGE XN_MASK_SENSOR_READ "Image" +#define XN_MASK_SENSOR_READ_DEPTH XN_MASK_SENSOR_READ "Depth" +#define XN_MASK_SENSOR_READ_AUDIO XN_MASK_SENSOR_READ "Audio" +#define XN_DUMP_AUDIO_IN "AudioIn" +#define XN_DUMP_IMAGE_IN "ImageIn" +#define XN_DUMP_DEPTH_IN "DepthIn" +#define XN_DUMP_MINI_PACKETS "MiniPackets" +#define XN_DUMP_TIMESTAMPS "SensorTimestamps" +#define XN_DUMP_BANDWIDTH "SensorBandwidth" +#define XN_DUMP_BAD_IMAGE "BadImage" +#define XN_DUMP_FRAME_SYNC "FrameSync" +#define XN_DUMP_SENSOR_LOG "SensorLog" + +//--------------------------------------------------------------------------- +// Forward Declarations +//--------------------------------------------------------------------------- +class XnSensorFirmware; +struct XnDevicePrivateData; +class XnSensorFixedParams; +class XnSensorFPS; +class XnCmosInfo; + +//--------------------------------------------------------------------------- +// Structures & Enums +//--------------------------------------------------------------------------- + +typedef struct XnSensorObjects +{ + XnSensorObjects(XnSensorFirmware* pFirmware, XnDevicePrivateData* pDevicePrivateData, XnSensorFPS* pFPS, XnCmosInfo* pCmosInfo) : + pFirmware(pFirmware), + pDevicePrivateData(pDevicePrivateData), + pFPS(pFPS), + pCmosInfo(pCmosInfo) + {} + + XnSensorFirmware* pFirmware; + XnDevicePrivateData* pDevicePrivateData; + XnSensorFPS* pFPS; + XnCmosInfo* pCmosInfo; +} XnSensorObjects; + +typedef struct XnHWInfo +{ + XnHWVer nHWVer; +} XnHWInfo; + +typedef struct XnChipInfo +{ + XnChipVer nChipVer; +} XnChipInfo; + +typedef enum { + RGBREG_NONE = 0, + RGBREG_FIX_IMAGE = 1, + RGBREG_FIX_DEPTH = 2 +} XnDeviceSensorRGBRegType; + +typedef struct +{ + XN_THREAD_HANDLE hThread; + XnBool bKillThread; + XnBool bThreadAlive; +} XnDeviceSensorThreadContext; + +typedef struct XnRegistrationFunctionCoefficients +{ + XnDouble dA; + XnDouble dB; + XnDouble dC; + XnDouble dD; + XnDouble dE; + XnDouble dF; +} XnRegistrationFunctionCoefficients; + +typedef struct +{ + /* Is this the first time timestamp is calculated. */ + XnBool bFirst; + /* The device TS which we use as reference for calculation. */ + XnUInt32 nReferenceTS; + /* The time corresponding to the TS in nReferenceTS. */ + XnUInt64 nTotalTicksAtReferenceTS; + /* The last device TS received. */ + XnUInt32 nLastDeviceTS; + /* The last result time calculated. */ + XnUInt64 nLastResultTime; + /* Stream name - for debug purposes. */ + const XnChar* csStreamName; +} XnTimeStampData; + +typedef struct XnDeviceSensorGMCPoint +{ + XnUInt16 m_X; + XnUInt16 m_Y; + XnUInt16 m_DX; + XnInt16 m_DY; + XnUInt16 m_Score; +} XnDeviceSensorGMCPoint; + +typedef struct XnCmosBlankingCoefficients +{ + XnFloat fA; + XnFloat fB; +} XnCmosBlankingCoefficients; + +typedef struct XnCmosBlankingInformation +{ + XnCmosBlankingCoefficients Coefficients[2]; +} XnCmosBlankingInformation; + +typedef struct XnDeviceInformation +{ + XnChar strDeviceName[128]; + XnChar strVendorData[128]; +} XnDeviceInformation; + +typedef XnStatus (XN_CALLBACK_TYPE* NewAudioDataCallback)(void* pCookie); + +struct XnSpecificUsbDevice; // Forward Declaration +class XnSensor; // Forward Declaration + +typedef struct XnDeviceAudioBuffer +{ + XN_CRITICAL_SECTION_HANDLE hLock; + /** A single (big) buffer for audio. */ + XnUInt8* pAudioBuffer; + /** An array of pointers into the audio buffer. */ + XnUInt64* pAudioPacketsTimestamps; + /** The index of the next packet that should be written. */ + volatile XnUInt32 nAudioWriteIndex; + /** The index of the next packet that can be read. */ + volatile XnUInt32 nAudioReadIndex; + /** Size of the audio buffer, in packets. */ + XnUInt32 nAudioBufferNumOfPackets; + /** Size of the audio buffer, in bytes. */ + XnUInt32 nAudioBufferSize; + XnUInt32 nAudioPacketSize; + /** A callback for new data */ + NewAudioDataCallback pAudioCallback; + void* pAudioCallbackCookie; +} XnDeviceAudioBuffer; + +typedef struct XnDevicePrivateData +{ + XnVersions Version; + + XN_SENSOR_HANDLE SensorHandle; + XnFirmwareInfo FWInfo; + XnHWInfo HWInfo; + XnChipInfo ChipInfo; + + XnSpecificUsbDevice* pSpecificDepthUsb; + XnSpecificUsbDevice* pSpecificImageUsb; + XnSpecificUsbDevice* pSpecificMiscUsb; + + XnFloat fDeviceFrequency; + + /** Keeps the global reference TS (the one marking time-zero). */ + XnUInt32 nGlobalReferenceTS; + /** Keeps the OS time of global reference TS. */ + XnUInt64 nGlobalReferenceOSTime; + + /** A general critical section used to synch end-points threads. */ + XN_CRITICAL_SECTION_HANDLE hEndPointsCS; + + /** Used to dump timestamps data. */ + XnDumpFile* TimestampsDump; + /** Used to dump bandwidth data. */ + XnDumpFile* BandwidthDump; + /** Used to dump MiniPackets data. */ + XnDumpFile* MiniPacketsDump; + + XnSensor* pSensor; + + XN_MUTEX_HANDLE hExecuteMutex; + + XnDeviceSensorThreadContext LogThread; + /** GMC Mode. */ + XnUInt32 nGMCMode; + XnBool bWavelengthCorrectionEnabled; + XnBool bWavelengthCorrectionDebugEnabled; + +} XnDevicePrivateData; + +#pragma pack (push, 1) + +typedef struct XnFixedParams +{ + // Misc + XnInt32 nSerialNumber; + XnInt32 nWatchDogTimeout; + + // Flash + XnInt32 nFlashType; + XnInt32 nFlashSize; + XnInt32 nFlashBurstEnable; + XnInt32 nFmifReadBurstCycles; + XnInt32 nFmifReadAccessCycles; + XnInt32 nFmifReadRecoverCycles; + XnInt32 nFmifWriteAccessCycles; + XnInt32 nFmifWriteRecoverCycles; + XnInt32 nFmifWriteAssertionCycles; + + // Audio + XnInt32 nI2SLogicClockPolarity; + + // Depth + XnInt32 nDepthCiuHorizontalSyncPolarity; + XnInt32 nDepthCiuVerticalSyncPolarity; + XnInt32 nDepthCmosType; + XnInt32 nDepthCmosI2CAddress; + XnInt32 nDepthCmosI2CBus; + + // Image + XnInt32 nImageCiuHorizontalSyncPolarity; + XnInt32 nImageCiuVerticalSyncPolarity; + XnInt32 nImageCmosType; + XnInt32 nImageCmosI2CAddress; + XnInt32 nImageCmosI2CBus; + + // Geometry + XnInt32 nIrCmosCloseToProjector; + XnFloat fDCmosEmitterDistance; + XnFloat fDCmosRCmosDistance; + XnFloat fReferenceDistance; + XnFloat fReferencePixelSize; + + // Clocks + XnInt32 nPllValue; + XnInt32 nSystemClockDivider; + XnInt32 nRCmosClockDivider; + XnInt32 nDCmosClockDivider; + XnInt32 nAdcClocDivider; + XnInt32 nI2CStandardSpeedHCount; + XnInt32 nI2CStandardSpeedLCount; + + XnInt32 nI2CHoldFixDelay; + + XnInt32 nSensorType; + XnInt32 nDebugMode; + XnInt32 nUseExtPhy; + XnInt32 bProjectorProtectionEnabled; + XnInt32 nProjectorDACOutputVoltage; + XnInt32 nProjectorDACOutputVoltage2; + XnInt32 nTecEmitterDelay; +} XnFixedParams; + +typedef struct XnFixedParamsV26 +{ + // Misc + XnInt32 nSerialNumber; + XnInt32 nWatchDogTimeout; + + // Flash + XnInt32 nFlashType; + XnInt32 nFlashSize; + XnInt32 nFlashBurstEnable; + XnInt32 nFmifReadBurstCycles; + XnInt32 nFmifReadAccessCycles; + XnInt32 nFmifReadRecoverCycles; + XnInt32 nFmifWriteAccessCycles; + XnInt32 nFmifWriteRecoverCycles; + XnInt32 nFmifWriteAssertionCycles; + + // Audio + XnInt32 nI2SLogicClockPolarity; + + // Depth + XnInt32 nDepthCiuHorizontalSyncPolarity; + XnInt32 nDepthCiuVerticalSyncPolarity; + XnInt32 nDepthCmosType; + XnInt32 nDepthCmosI2CAddress; + XnInt32 nDepthCmosI2CBus; + + // Image + XnInt32 nImageCiuHorizontalSyncPolarity; + XnInt32 nImageCiuVerticalSyncPolarity; + XnInt32 nImageCmosType; + XnInt32 nImageCmosI2CAddress; + XnInt32 nImageCmosI2CBus; + + // Geometry + XnInt32 nIrCmosCloseToProjector; + XnFloat fDCmosEmitterDistance; + XnFloat fDCmosRCmosDistance; + XnFloat fReferenceDistance; + XnFloat fReferencePixelSize; + + // Clocks + XnInt32 nPllValue; + XnInt32 nSystemClockDivider; + XnInt32 nRCmosClockDivider; + XnInt32 nDCmosClockDivider; + XnInt32 nAdcClocDivider; + XnInt32 nI2CStandardSpeedHCount; + XnInt32 nI2CStandardSpeedLCount; + + XnInt32 nI2CHoldFixDelay; + + XnInt32 nSensorType; + XnInt32 nDebugMode; + XnInt32 nTecEmitterDelay; + XnInt32 nUseExtPhy; +} XnFixedParamsV26; + +typedef struct XnFixedParamsV20 +{ + // Misc + XnInt32 nSerialNumber; + XnInt32 nWatchDogTimeout; + + // Flash + XnInt32 nFlashType; + XnInt32 nFlashSize; + XnInt32 nFlashBurstEnable; + XnInt32 nFmifReadBurstCycles; + XnInt32 nFmifReadAccessCycles; + XnInt32 nFmifReadRecoverCycles; + XnInt32 nFmifWriteAccessCycles; + XnInt32 nFmifWriteRecoverCycles; + XnInt32 nFmifWriteAssertionCycles; + + // Audio + XnInt32 nI2SLogicClockPolarity; + + // Depth + XnInt32 nDepthCiuHorizontalSyncPolarity; + XnInt32 nDepthCiuVerticalSyncPolarity; + XnInt32 nDepthCmosType; + XnInt32 nDepthCmosI2CAddress; + XnInt32 nDepthCmosI2CBus; + + // Image + XnInt32 nImageCiuHorizontalSyncPolarity; + XnInt32 nImageCiuVerticalSyncPolarity; + XnInt32 nImageCmosType; + XnInt32 nImageCmosI2CAddress; + XnInt32 nImageCmosI2CBus; + + // Geometry + XnInt32 nIrCmosCloseToProjector; + XnFloat fDCmosEmitterDistance; + XnFloat fDCmosRCmosDistance; + XnFloat fReferenceDistance; + XnFloat fReferencePixelSize; + + // Clocks + XnInt32 nPllValue; + XnInt32 nSystemClockDivider; + XnInt32 nRCmosClockDivider; + XnInt32 nDCmosClockDivider; + XnInt32 nAdcClocDivider; + XnInt32 nI2CStandardSpeedHCount; + XnInt32 nI2CStandardSpeedLCount; + + XnInt32 nI2CHoldFixDelay; + + XnInt32 nSensorType; + XnInt32 nDebugMode; + XnInt32 nTecEmitterDelay; +} XnFixedParamsV20; + +typedef struct XnRegistrationInformation1000 +{ + XnRegistrationFunctionCoefficients FuncX; + XnRegistrationFunctionCoefficients FuncY; + XnDouble dBeta; +} XnRegistrationInformation1000; + +typedef struct XnRegistrationInformation1080 +{ + XnInt32 nRGS_DX_CENTER; + XnInt32 nRGS_AX; + XnInt32 nRGS_BX; + XnInt32 nRGS_CX; + XnInt32 nRGS_DX; + XnInt32 nRGS_DX_START; + XnInt32 nRGS_AY; + XnInt32 nRGS_BY; + XnInt32 nRGS_CY; + XnInt32 nRGS_DY; + XnInt32 nRGS_DY_START; + XnInt32 nRGS_DX_BETA_START; + XnInt32 nRGS_DY_BETA_START; + XnInt32 nRGS_ROLLOUT_BLANK; + XnInt32 nRGS_ROLLOUT_SIZE; + XnInt32 nRGS_DX_BETA_INC; + XnInt32 nRGS_DY_BETA_INC; + XnInt32 nRGS_DXDX_START; + XnInt32 nRGS_DXDY_START; + XnInt32 nRGS_DYDX_START; + XnInt32 nRGS_DYDY_START; + XnInt32 nRGS_DXDXDX_START; + XnInt32 nRGS_DYDXDX_START; + XnInt32 nRGS_DXDXDY_START; + XnInt32 nRGS_DYDXDY_START; + XnInt32 nBACK_COMP1; + XnInt32 nRGS_DYDYDX_START; + XnInt32 nBACK_COMP2; + XnInt32 nRGS_DYDYDY_START; +} XnRegistrationInformation1080; + +typedef struct XnRegistrationPaddingInformation +{ + XnUInt16 nStartLines; + XnUInt16 nEndLines; + XnUInt16 nCroppingLines; +} XnRegistrationPaddingInformation; + +typedef struct XnDepthInformation +{ + XnUInt16 nConstShift; +} XnDepthInformation; + +typedef struct XnFrequencyInformation +{ + XnFloat fDeviceFrequency; +} XnFrequencyInformation; + +typedef struct XnAudioSharedBuffer +{ + XnUInt32 nPacketCount; + XnUInt32 nPacketSize; + XnUInt32 nWritePacketIndex; +} XnAudioSharedBuffer; + +#pragma pack (pop) + +XnStatus XnDeviceSensorSetParam(XnDevicePrivateData* pDevicePrivateData, const XnChar* cpParamName, const XnInt32 nValue); + +#endif diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.cpp b/Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.cpp new file mode 100644 index 0000000..40c4e74 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.cpp @@ -0,0 +1,347 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensorIO.h" +#include "XnDeviceSensor.h" +#include +#include "XnDeviceEnumeration.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnSensorIO::XnSensorIO(XN_SENSOR_HANDLE* pSensorHandle) : + m_pSensorHandle(pSensorHandle), + m_bMiscSupported(FALSE), + m_bIsLowBandwidth(FALSE) +{ +} + +XnSensorIO::~XnSensorIO() +{ +} + +XnStatus XnSensorIO::OpenDevice(const XnChar* strPath) +{ + XnStatus nRetVal; + + xnLogVerbose(XN_MASK_DEVICE_IO, "Connecting to USB device..."); + + // try to open the device + xnLogVerbose(XN_MASK_DEVICE_IO, "Trying to open sensor '%s'...", strPath); + nRetVal = xnUSBOpenDeviceByPath(strPath, &m_pSensorHandle->USBDevice); + XN_IS_STATUS_OK(nRetVal); + + // on older firmwares, control was sent over BULK endpoints. Check if this is the case + xnLogVerbose(XN_MASK_DEVICE_IO, "Trying to open endpoint 0x4 for control out (for old firmwares)..."); + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, 0x4, XN_USB_EP_BULK, XN_USB_DIRECTION_OUT, &m_pSensorHandle->ControlConnection.ControlOutConnectionEp); + if (nRetVal == XN_STATUS_USB_ENDPOINT_NOT_FOUND || nRetVal == XN_STATUS_USB_WRONG_ENDPOINT_TYPE || nRetVal == XN_STATUS_USB_WRONG_ENDPOINT_DIRECTION) + { + // this is not the case. use regular control endpoint (0) + m_pSensorHandle->ControlConnection.bIsBulk = FALSE; + } + else + { + XN_IS_STATUS_OK(nRetVal); + + xnLogVerbose(XN_MASK_DEVICE_IO, "Opening endpoint 0x85 for control in..."); + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, 0x85, XN_USB_EP_BULK, XN_USB_DIRECTION_IN, &m_pSensorHandle->ControlConnection.ControlInConnectionEp); + XN_IS_STATUS_OK(nRetVal); + + m_pSensorHandle->ControlConnection.bIsBulk = TRUE; + } + + nRetVal = XnDeviceEnumeration::IsSensorLowBandwidth(strPath, &m_bIsLowBandwidth); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_DEVICE_IO, "Connected to USB device%s", m_bIsLowBandwidth ? " (LowBand)" : ""); + + strcpy(m_strDeviceName, strPath); + + return XN_STATUS_OK; +} + +XnStatus XnSensorIO::OpenDataEndPoints(XnSensorUsbInterface nInterface, const XnFirmwareInfo& fwInfo) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt8 nAlternativeInterface = 0; + + // try to set requested interface + if (nInterface != XN_SENSOR_USB_INTERFACE_DEFAULT) + { + switch (nInterface) + { + case XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS: + nAlternativeInterface = fwInfo.nISOAlternativeInterface; + break; + case XN_SENSOR_USB_INTERFACE_BULK_ENDPOINTS: + nAlternativeInterface = fwInfo.nBulkAlternativeInterface; + break; + case XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS_LOW_DEPTH: + nAlternativeInterface = fwInfo.nISOLowDepthAlternativeInterface; + break; + default: + XN_ASSERT(FALSE); + XN_LOG_WARNING_RETURN(XN_STATUS_USB_INTERFACE_NOT_SUPPORTED, XN_MASK_DEVICE_IO, "Unknown interface type: %d", nInterface); + } + + if (nAlternativeInterface == (XnUInt8)-1) + { + XN_ASSERT(FALSE); + XN_LOG_WARNING_RETURN(XN_STATUS_USB_INTERFACE_NOT_SUPPORTED, XN_MASK_DEVICE_IO, "Interface %d is not supported by firmware", nInterface); + } + + xnLogVerbose(XN_MASK_DEVICE_IO, "Setting USB alternative interface to %d...", nAlternativeInterface); + nRetVal = xnUSBSetInterface(m_pSensorHandle->USBDevice, 0, nAlternativeInterface); + XN_IS_STATUS_OK(nRetVal); + } + + xnLogVerbose(XN_MASK_DEVICE_IO, "Opening endpoints..."); + + // up until v3.0/4.0, Image went over 0x82, depth on 0x83, audio on 0x86, and control was using bulk EPs, at 0x4 and 0x85. + // starting v3.0/4.0, Image is at 0x81, depth at 0x82, audio/misc at 0x83, and control is using actual control EPs. + // This means we are using the new Jungo USB Code + XnBool bNewUSB = TRUE; + + // Depth + XnBool bIsISO = FALSE; + + xnLogVerbose(XN_MASK_DEVICE_IO, "Opening endpoint 0x81 for depth..."); + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, 0x81, XN_USB_EP_BULK, XN_USB_DIRECTION_IN, &m_pSensorHandle->DepthConnection.UsbEp); + if (nRetVal == XN_STATUS_USB_ENDPOINT_NOT_FOUND) + { + bNewUSB = FALSE; + xnLogVerbose(XN_MASK_DEVICE_IO, "Endpoint 0x81 does not exist. Trying old USB: Opening 0x82 for depth..."); + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, 0x82, XN_USB_EP_BULK, XN_USB_DIRECTION_IN, &m_pSensorHandle->DepthConnection.UsbEp); + XN_IS_STATUS_OK(nRetVal); + } + else + { + if (nRetVal == XN_STATUS_USB_WRONG_ENDPOINT_TYPE) + { + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, 0x81, XN_USB_EP_ISOCHRONOUS, XN_USB_DIRECTION_IN, &m_pSensorHandle->DepthConnection.UsbEp); + + bIsISO = TRUE; + } + + bNewUSB = TRUE; + + XN_IS_STATUS_OK(nRetVal); + + if (bIsISO) + { + xnLogVerbose(XN_MASK_DEVICE_IO, "Depth endpoint is isochronous."); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_IO, "Depth endpoint is bulk."); + } + } + m_pSensorHandle->DepthConnection.bIsOpen = TRUE; + + nRetVal = xnUSBGetEndPointMaxPacketSize(m_pSensorHandle->DepthConnection.UsbEp, &m_pSensorHandle->DepthConnection.nMaxPacketSize); + XN_IS_STATUS_OK(nRetVal); + + // Image + XnUInt16 nImageEP = bNewUSB ? 0x82 : 0x83; + + bIsISO = FALSE; + + xnLogVerbose(XN_MASK_DEVICE_IO, "Opening endpoint 0x%hx for image...", nImageEP); + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, nImageEP, XN_USB_EP_BULK, XN_USB_DIRECTION_IN, &m_pSensorHandle->ImageConnection.UsbEp); + if (nRetVal == XN_STATUS_USB_WRONG_ENDPOINT_TYPE) + { + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, nImageEP, XN_USB_EP_ISOCHRONOUS, XN_USB_DIRECTION_IN, &m_pSensorHandle->ImageConnection.UsbEp); + + bIsISO = TRUE; + } + + XN_IS_STATUS_OK(nRetVal); + + if (bIsISO) + { + xnLogVerbose(XN_MASK_DEVICE_IO, "Image endpoint is isochronous."); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_IO, "Image endpoint is bulk."); + } + + m_pSensorHandle->ImageConnection.bIsOpen = TRUE; + + nRetVal = xnUSBGetEndPointMaxPacketSize(m_pSensorHandle->ImageConnection.UsbEp, &m_pSensorHandle->ImageConnection.nMaxPacketSize); + XN_IS_STATUS_OK(nRetVal); + + // Misc + XnUInt16 nMiscEP = bNewUSB ? 0x83 : 0x86; + + bIsISO = FALSE; + + xnLogVerbose(XN_MASK_DEVICE_IO, "Opening endpoint 0x%hx for misc...", nMiscEP); + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, nMiscEP, XN_USB_EP_BULK, XN_USB_DIRECTION_IN, &m_pSensorHandle->MiscConnection.UsbEp); + if (nRetVal == XN_STATUS_USB_WRONG_ENDPOINT_TYPE) + { + nRetVal = xnUSBOpenEndPoint(m_pSensorHandle->USBDevice, nMiscEP, XN_USB_EP_ISOCHRONOUS, XN_USB_DIRECTION_IN, &m_pSensorHandle->MiscConnection.UsbEp); + + bIsISO = TRUE; + } + if (nRetVal == XN_STATUS_USB_ENDPOINT_NOT_FOUND) + { + // Firmware does not support misc... + m_pSensorHandle->MiscConnection.bIsOpen = FALSE; + m_bMiscSupported = FALSE; + + xnLogVerbose(XN_MASK_DEVICE_IO, "Misc endpoint is not supported..."); + } + else if (nRetVal == XN_STATUS_OK) + { + m_pSensorHandle->MiscConnection.bIsOpen = TRUE; + m_bMiscSupported = TRUE; + + if (bIsISO) + { + xnLogVerbose(XN_MASK_DEVICE_IO, "Misc endpoint is isochronous."); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_IO, "Misc endpoint is bulk."); + } + } + else + { + return nRetVal; + } + + if (m_pSensorHandle->MiscConnection.bIsOpen) + { + nRetVal = xnUSBGetEndPointMaxPacketSize(m_pSensorHandle->MiscConnection.UsbEp, &m_pSensorHandle->MiscConnection.nMaxPacketSize); + XN_IS_STATUS_OK(nRetVal); + } + + xnLogInfo(XN_MASK_DEVICE_IO, "Endpoints open"); + + // All is good... + return (XN_STATUS_OK); +} + +XnStatus XnSensorIO::CloseDevice() +{ + XnStatus nRetVal; + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down USB depth read thread..."); + xnUSBShutdownReadThread(m_pSensorHandle->DepthConnection.UsbEp); + + if (m_pSensorHandle->DepthConnection.UsbEp != NULL) + { + nRetVal = xnUSBCloseEndPoint(m_pSensorHandle->DepthConnection.UsbEp); + XN_IS_STATUS_OK(nRetVal); + m_pSensorHandle->DepthConnection.UsbEp = NULL; + } + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down USB image read thread..."); + xnUSBShutdownReadThread(m_pSensorHandle->ImageConnection.UsbEp); + + if (m_pSensorHandle->ImageConnection.UsbEp != NULL) + { + nRetVal = xnUSBCloseEndPoint(m_pSensorHandle->ImageConnection.UsbEp); + XN_IS_STATUS_OK(nRetVal); + m_pSensorHandle->ImageConnection.UsbEp = NULL; + } + + if (m_pSensorHandle->MiscConnection.bIsOpen) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down USB misc read thread..."); + xnUSBShutdownReadThread(m_pSensorHandle->MiscConnection.UsbEp); + + if (m_pSensorHandle->MiscConnection.UsbEp != NULL) + { + nRetVal = xnUSBCloseEndPoint(m_pSensorHandle->MiscConnection.UsbEp); + XN_IS_STATUS_OK(nRetVal); + m_pSensorHandle->MiscConnection.UsbEp = NULL; + } + } + + if (m_pSensorHandle->ControlConnection.bIsBulk) + { + if (m_pSensorHandle->ControlConnection.ControlInConnectionEp != NULL) + { + nRetVal = xnUSBCloseEndPoint(m_pSensorHandle->ControlConnection.ControlInConnectionEp); + XN_IS_STATUS_OK(nRetVal); + m_pSensorHandle->ControlConnection.ControlInConnectionEp = NULL; + } + + if (m_pSensorHandle->ControlConnection.ControlOutConnectionEp != NULL) + { + nRetVal = xnUSBCloseEndPoint(m_pSensorHandle->ControlConnection.ControlOutConnectionEp); + XN_IS_STATUS_OK(nRetVal); + m_pSensorHandle->ControlConnection.ControlOutConnectionEp = NULL; + } + } + + if (m_pSensorHandle->USBDevice != NULL) + { + nRetVal = xnUSBCloseDevice(m_pSensorHandle->USBDevice); + XN_IS_STATUS_OK(nRetVal); + m_pSensorHandle->USBDevice = NULL; + } + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Device closed successfully"); + + // All is good... + return (XN_STATUS_OK); +} + +const XnChar* XnSensorIO::GetDevicePath() +{ + return m_strDeviceName; +} + +XnSensorUsbInterface XnSensorIO::GetCurrentInterface(const XnFirmwareInfo& fwInfo) const +{ + XnUInt8 nActualInterface = 0; + XnUInt8 nAlternativeInterface = 0; + XnStatus nRetVal = xnUSBGetInterface(m_pSensorHandle->USBDevice, &nActualInterface, &nAlternativeInterface); + if (nRetVal != XN_STATUS_OK) + { + XN_ASSERT(FALSE); + return (XnSensorUsbInterface)-1; + } + + if (nAlternativeInterface == fwInfo.nISOAlternativeInterface) + { + return XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS; + } + else if (nAlternativeInterface == fwInfo.nBulkAlternativeInterface) + { + return XN_SENSOR_USB_INTERFACE_BULK_ENDPOINTS; + } + else if (nAlternativeInterface == fwInfo.nISOLowDepthAlternativeInterface) + { + return XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS_LOW_DEPTH; + } + else + { + XN_ASSERT(FALSE); + xnLogError(XN_MASK_DEVICE_IO, "Unexpected alternative interface: %d", nAlternativeInterface); + return (XnSensorUsbInterface)-1; + } +} diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.h b/Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.h new file mode 100644 index 0000000..bb58ea1 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceSensorIO.h @@ -0,0 +1,100 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_DEVICE_SENSOR_I_O_H__ +#define __XN_DEVICE_SENSOR_I_O_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include "XnFirmwareInfo.h" +#include +#include + +//--------------------------------------------------------------------------- +// Structures & Enums +//--------------------------------------------------------------------------- +typedef struct XnUsbConnection +{ + XN_USB_EP_HANDLE UsbEp; + + XnBool bIsOpen; + XnUInt8* pUSBBuffer; + XnUInt32 nUSBBufferReadOffset; + XnUInt32 nUSBBufferWriteOffset; + XnUInt32 nMaxPacketSize; +} XnUsbConnection; + +typedef struct XnUsbControlConnection +{ + /* When true, control connection is implemented using bulk end points. */ + XnBool bIsBulk; + XN_USB_EP_HANDLE ControlOutConnectionEp; + XN_USB_EP_HANDLE ControlInConnectionEp; +} XnUsbControlConnection; + +typedef struct XN_SENSOR_HANDLE +{ + XN_USB_DEV_HANDLE USBDevice; + + XnUsbControlConnection ControlConnection; + XnUsbConnection DepthConnection; + XnUsbConnection ImageConnection; + XnUsbConnection MiscConnection; + XnUInt8 nBoardVer; +} XN_SENSOR_HANDLE; + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +class XnSensorIO +{ +public: + XnSensorIO(XN_SENSOR_HANDLE* pSensorHandle); + ~XnSensorIO(); + + XnStatus OpenDevice(const XnChar* strPath); + + XnStatus OpenDataEndPoints(XnSensorUsbInterface nInterface, const XnFirmwareInfo& fwInfo); + + XnSensorUsbInterface GetCurrentInterface(const XnFirmwareInfo& fwInfo) const; + + XnStatus CloseDevice(); + + inline XnBool IsMiscEndpointSupported() const { return m_bMiscSupported; } + inline XnBool IsLowBandwidth() const { return m_bIsLowBandwidth; } + + const XnChar* GetDevicePath(); + +private: + XN_SENSOR_HANDLE* m_pSensorHandle; + XnBool m_bMiscSupported; + XnChar m_strDeviceName[XN_DEVICE_MAX_STRING_LENGTH]; + XnBool m_bIsLowBandwidth; + XnUSBEventCallbackFunctionPtr m_pCallbackPtr; + void* m_pCallbackData; +}; + +#endif //__XN_DEVICE_SENSOR_I_O_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.cpp b/Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.cpp new file mode 100644 index 0000000..b3a2ad8 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.cpp @@ -0,0 +1,247 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" +#include "XnDeviceSensorInit.h" +#include "XnDeviceSensorProtocol.h" +#include "Bayer.h" +#include "XnHostProtocol.h" +#include +#include "XnSensor.h" + +#define XN_HOST_PROTOCOL_MUTEX_NAME_PREFIX "HostProtocolMutex" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnDeviceSensorInit(XnDevicePrivateData* pDevicePrivateData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnDeviceSensorAllocateBuffers(pDevicePrivateData); + XN_IS_STATUS_OK(nRetVal); + +#if XN_PLATFORM == XN_PLATFORM_ANDROID_ARM + nRetVal = xnOSCreateMutex(&pDevicePrivateData->hExecuteMutex); + XN_IS_STATUS_OK(nRetVal); +#else + XnChar strMutexName[XN_FILE_MAX_PATH]; + XnUInt32 nCharsWritten = 0; + nRetVal = xnOSStrFormat(strMutexName, XN_FILE_MAX_PATH, &nCharsWritten, "%s%s", XN_HOST_PROTOCOL_MUTEX_NAME_PREFIX, pDevicePrivateData->pSensor->GetUSBPath()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = xnOSCreateNamedMutex(&pDevicePrivateData->hExecuteMutex, strMutexName); + XN_IS_STATUS_OK(nRetVal); +#endif + + nRetVal = XnDeviceSensorConfigureVersion(pDevicePrivateData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceSensorOpenInputThreads(XnDevicePrivateData* pDevicePrivateData) +{ + XnSensorUsbInterface usbInterface = pDevicePrivateData->pSensor->GetCurrentUsbInterface(); + + // common stuff + pDevicePrivateData->pSpecificDepthUsb = (XnSpecificUsbDevice*)xnOSMallocAligned(sizeof(XnSpecificUsbDevice), XN_DEFAULT_MEM_ALIGN); + pDevicePrivateData->pSpecificDepthUsb->pDevicePrivateData = pDevicePrivateData; + pDevicePrivateData->pSpecificDepthUsb->pUsbConnection = &pDevicePrivateData->SensorHandle.DepthConnection; + pDevicePrivateData->pSpecificDepthUsb->CurrState.State = XN_WAITING_FOR_CONFIGURATION; + pDevicePrivateData->pSpecificDepthUsb->nIgnoreBytes = (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_0) ? 0 : pDevicePrivateData->pSpecificDepthUsb->nChunkReadBytes; + + pDevicePrivateData->pSpecificImageUsb = (XnSpecificUsbDevice*)xnOSMallocAligned(sizeof(XnSpecificUsbDevice), XN_DEFAULT_MEM_ALIGN); + pDevicePrivateData->pSpecificImageUsb->pDevicePrivateData = pDevicePrivateData; + pDevicePrivateData->pSpecificImageUsb->pUsbConnection = &pDevicePrivateData->SensorHandle.ImageConnection; + pDevicePrivateData->pSpecificImageUsb->CurrState.State = XN_WAITING_FOR_CONFIGURATION; + pDevicePrivateData->pSpecificImageUsb->nIgnoreBytes = (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_0) ? 0 : pDevicePrivateData->pSpecificImageUsb->nChunkReadBytes; + + pDevicePrivateData->pSpecificMiscUsb = (XnSpecificUsbDevice*)xnOSMallocAligned(sizeof(XnSpecificUsbDevice), XN_DEFAULT_MEM_ALIGN); + pDevicePrivateData->pSpecificMiscUsb->pDevicePrivateData = pDevicePrivateData; + pDevicePrivateData->pSpecificMiscUsb->pUsbConnection = &pDevicePrivateData->SensorHandle.MiscConnection; + pDevicePrivateData->pSpecificMiscUsb->CurrState.State = XN_WAITING_FOR_CONFIGURATION; + pDevicePrivateData->pSpecificMiscUsb->nIgnoreBytes = (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_0) ? 0 : pDevicePrivateData->pSpecificMiscUsb->nChunkReadBytes; + + // timeout + if (usbInterface == XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS || usbInterface == XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS_LOW_DEPTH) + { + pDevicePrivateData->pSpecificDepthUsb->nTimeout = XN_SENSOR_READ_THREAD_TIMEOUT_ISO; + pDevicePrivateData->pSpecificImageUsb->nTimeout = XN_SENSOR_READ_THREAD_TIMEOUT_ISO; + pDevicePrivateData->pSpecificMiscUsb->nTimeout = XN_SENSOR_READ_THREAD_TIMEOUT_ISO; + } + else + { + pDevicePrivateData->pSpecificDepthUsb->nTimeout = XN_SENSOR_READ_THREAD_TIMEOUT_BULK; + pDevicePrivateData->pSpecificImageUsb->nTimeout = XN_SENSOR_READ_THREAD_TIMEOUT_BULK; + pDevicePrivateData->pSpecificMiscUsb->nTimeout = XN_SENSOR_READ_THREAD_TIMEOUT_BULK; + } + + // buffer size + if (usbInterface == XN_SENSOR_USB_INTERFACE_BULK_ENDPOINTS) + { + pDevicePrivateData->pSpecificDepthUsb->nChunkReadBytes = XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_BULK * pDevicePrivateData->SensorHandle.DepthConnection.nMaxPacketSize; + pDevicePrivateData->pSpecificImageUsb->nChunkReadBytes = XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_BULK * pDevicePrivateData->SensorHandle.ImageConnection.nMaxPacketSize; + pDevicePrivateData->pSpecificMiscUsb->nChunkReadBytes = XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_BULK * pDevicePrivateData->SensorHandle.MiscConnection.nMaxPacketSize; + } + else if (pDevicePrivateData->pSensor->IsLowBandwidth()) + { + pDevicePrivateData->pSpecificDepthUsb->nChunkReadBytes = XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO * pDevicePrivateData->SensorHandle.DepthConnection.nMaxPacketSize; + pDevicePrivateData->pSpecificImageUsb->nChunkReadBytes = XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO * pDevicePrivateData->SensorHandle.ImageConnection.nMaxPacketSize; + pDevicePrivateData->pSpecificMiscUsb->nChunkReadBytes = XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO * pDevicePrivateData->SensorHandle.MiscConnection.nMaxPacketSize; + } + else + { + pDevicePrivateData->pSpecificDepthUsb->nChunkReadBytes = XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_ISO * pDevicePrivateData->SensorHandle.DepthConnection.nMaxPacketSize; + pDevicePrivateData->pSpecificImageUsb->nChunkReadBytes = XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_ISO * pDevicePrivateData->SensorHandle.ImageConnection.nMaxPacketSize; + pDevicePrivateData->pSpecificMiscUsb->nChunkReadBytes = XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_ISO * pDevicePrivateData->SensorHandle.MiscConnection.nMaxPacketSize; + } + + // number of buffers + pDevicePrivateData->pSpecificImageUsb->nNumberOfBuffers = XN_SENSOR_USB_IMAGE_BUFFERS; + pDevicePrivateData->pSpecificMiscUsb->nNumberOfBuffers = XN_SENSOR_USB_MISC_BUFFERS; + if (usbInterface == XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS_LOW_DEPTH) + { + pDevicePrivateData->pSpecificDepthUsb->nNumberOfBuffers = XN_SENSOR_USB_DEPTH_BUFFERS_LOW_ISO; + } + else + { + pDevicePrivateData->pSpecificDepthUsb->nNumberOfBuffers = XN_SENSOR_USB_DEPTH_BUFFERS; + } + + // Switch depth & image EPs for older FWs + if (pDevicePrivateData->FWInfo.nFWVer <= XN_SENSOR_FW_VER_5_1) + { + XnSpecificUsbDevice* pTempUsbDevice = pDevicePrivateData->pSpecificDepthUsb; + pDevicePrivateData->pSpecificDepthUsb = pDevicePrivateData->pSpecificImageUsb; + pDevicePrivateData->pSpecificImageUsb = pTempUsbDevice; + } + + return XN_STATUS_OK; +} + +XnStatus XnDeviceSensorAllocateBuffers(XnDevicePrivateData* pDevicePrivateData) +{ + pDevicePrivateData->SensorHandle.DepthConnection.pUSBBuffer = (XnUInt8*)xnOSCallocAligned(XN_SENSOR_PROTOCOL_USB_BUFFER_SIZE, sizeof(XnUInt8), XN_DEFAULT_MEM_ALIGN); + pDevicePrivateData->SensorHandle.DepthConnection.nUSBBufferReadOffset = 0; + pDevicePrivateData->SensorHandle.DepthConnection.nUSBBufferWriteOffset = 0; + + pDevicePrivateData->SensorHandle.ImageConnection.pUSBBuffer = (XnUInt8*)xnOSCallocAligned(XN_SENSOR_PROTOCOL_USB_BUFFER_SIZE, sizeof(XnUInt8), XN_DEFAULT_MEM_ALIGN); + pDevicePrivateData->SensorHandle.ImageConnection.nUSBBufferReadOffset = 0; + pDevicePrivateData->SensorHandle.ImageConnection.nUSBBufferWriteOffset = 0; + + if (pDevicePrivateData->pSensor->IsMiscSupported()) + { + pDevicePrivateData->SensorHandle.MiscConnection.pUSBBuffer = (XnUInt8*)xnOSCallocAligned(XN_SENSOR_PROTOCOL_USB_BUFFER_SIZE, sizeof(XnUInt8), XN_DEFAULT_MEM_ALIGN); + pDevicePrivateData->SensorHandle.MiscConnection.nUSBBufferReadOffset = 0; + pDevicePrivateData->SensorHandle.MiscConnection.nUSBBufferWriteOffset = 0; + } + else + { + pDevicePrivateData->SensorHandle.MiscConnection.pUSBBuffer = NULL; + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceSensorFreeBuffers(XnDevicePrivateData* pDevicePrivateData) +{ + if (pDevicePrivateData->SensorHandle.DepthConnection.pUSBBuffer != NULL) + { + XN_ALIGNED_FREE_AND_NULL(pDevicePrivateData->SensorHandle.DepthConnection.pUSBBuffer); + } + + if (pDevicePrivateData->SensorHandle.ImageConnection.pUSBBuffer != NULL) + { + XN_ALIGNED_FREE_AND_NULL(pDevicePrivateData->SensorHandle.ImageConnection.pUSBBuffer); + } + + if (pDevicePrivateData->SensorHandle.MiscConnection.pUSBBuffer != NULL) + { + XN_ALIGNED_FREE_AND_NULL(pDevicePrivateData->SensorHandle.MiscConnection.pUSBBuffer); + } + + if (pDevicePrivateData->pSpecificDepthUsb != NULL) + { + XN_ALIGNED_FREE_AND_NULL(pDevicePrivateData->pSpecificDepthUsb); + } + + if (pDevicePrivateData->pSpecificImageUsb != NULL) + { + XN_ALIGNED_FREE_AND_NULL(pDevicePrivateData->pSpecificImageUsb); + } + + if (pDevicePrivateData->pSpecificMiscUsb != NULL) + { + XN_ALIGNED_FREE_AND_NULL(pDevicePrivateData->pSpecificMiscUsb); + } + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceSensorConfigureVersion(XnDevicePrivateData* pDevicePrivateData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // GetVersion is exactly the same in all versions, except a change that was made in version 5.1. + // so, we'll start with that, and if doesn't work we'll try previous protocols + XnHostProtocolUsbCore usb = XN_USB_CORE_JANGO; + nRetVal = XnHostProtocolInitFWParams(pDevicePrivateData, 5, 1, 0, usb, TRUE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolGetVersion(pDevicePrivateData, pDevicePrivateData->Version); + + // Strange bug: sometimes, when sending first command to device, no reply is received, so try again + if (nRetVal == XN_STATUS_USB_TRANSFER_TIMEOUT) + { + xnOSSleep(2000); + nRetVal = XnHostProtocolGetVersion(pDevicePrivateData, pDevicePrivateData->Version); + } + + // if command failed for any reason, try again with older protocol + if (nRetVal != XN_STATUS_OK) + { + nRetVal = XnHostProtocolInitFWParams(pDevicePrivateData, 5, 0, 0, usb, TRUE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolGetVersion(pDevicePrivateData, pDevicePrivateData->Version); + } + + // if it still fails, give up + XN_IS_STATUS_OK(nRetVal); + + // check which usb core is used (don't check error code. If this fails, assume JANGO + if (XN_STATUS_OK != XnHostProtocolGetUsbCoreType(pDevicePrivateData, usb)) + { + usb = XN_USB_CORE_JANGO; + } + + // Now that we have the actual version, configure protocol accordingly + nRetVal = XnHostProtocolInitFWParams(pDevicePrivateData, pDevicePrivateData->Version.nMajor, pDevicePrivateData->Version.nMinor, pDevicePrivateData->Version.nBuild, usb, FALSE); + XN_IS_STATUS_OK(nRetVal); + + pDevicePrivateData->HWInfo.nHWVer = pDevicePrivateData->Version.HWVer; + pDevicePrivateData->ChipInfo.nChipVer = pDevicePrivateData->Version.ChipVer; + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.h b/Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.h new file mode 100644 index 0000000..031eb1b --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceSensorInit.h @@ -0,0 +1,97 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_DEVICESENSORINIT_H_ +#define _XN_DEVICESENSORINIT_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" +#include "XnDeviceSensorProtocol.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- + +#if XN_PLATFORM == XN_PLATFORM_WIN32 + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_ISO 8*10 + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_BULK 120 + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO 8*5 + #define XN_SENSOR_USB_IMAGE_BUFFERS 8 + + #define XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_ISO 8*10 + #define XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_BULK 120 + #define XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO 8*5 + #define XN_SENSOR_USB_DEPTH_BUFFERS 8 + #define XN_SENSOR_USB_DEPTH_BUFFERS_LOW_ISO 4 + + #define XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_ISO 104 + #define XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_BULK 20 + #define XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO 52 + #define XN_SENSOR_USB_MISC_BUFFERS 8 +#elif XN_PLATFORM == XN_PLATFORM_PS3 + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_ISO 0x1E000 + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_BULK 0x4000 + #define XN_SENSOR_USB_IMAGE_BUFFERS 1 + + #define XN_SENSOR_USB_DEPTH_BUFFER_SIZE 0x4000 + #define XN_SENSOR_USB_DEPTH_BUFFERS 2 + + #define XN_SENSOR_USB_MISC_BUFFER_SIZE 0x1000 + #define XN_SENSOR_USB_MISC_BUFFERS 1 +#elif (XN_PLATFORM == XN_PLATFORM_LINUX_X86 || XN_PLATFORM == XN_PLATFORM_LINUX_ARM || XN_PLATFORM == XN_PLATFORM_MACOSX || XN_PLATFORM == XN_PLATFORM_ANDROID_ARM) + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_ISO 32 + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_BULK 40 + #define XN_SENSOR_USB_IMAGE_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO 16 + #define XN_SENSOR_USB_IMAGE_BUFFERS 16 + + #define XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_ISO 32 + #define XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_BULK 40 + #define XN_SENSOR_USB_DEPTH_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO 16 + #define XN_SENSOR_USB_DEPTH_BUFFERS 16 + #define XN_SENSOR_USB_DEPTH_BUFFERS_LOW_ISO 4 + + #define XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_ISO 104 + #define XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_BULK 20 + #define XN_SENSOR_USB_MISC_BUFFER_SIZE_MULTIPLIER_LOWBAND_ISO 52 + #define XN_SENSOR_USB_MISC_BUFFERS 5 +#endif + +#define XN_SENSOR_READ_THREAD_TIMEOUT_ISO 100 +#define XN_SENSOR_READ_THREAD_TIMEOUT_BULK 1000 + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +XnStatus XnDeviceSensorInit(XnDevicePrivateData* pDevicePrivateData); + +XnStatus XnDeviceSensorAllocateBuffers(XnDevicePrivateData* pDevicePrivateData); +XnStatus XnDeviceSensorFreeBuffers(XnDevicePrivateData* pDevicePrivateData); + +XnStatus XnDeviceSensorConfigureVersion(XnDevicePrivateData* pDevicePrivateData); + +XnStatus XnDeviceSensorOpenInputThreads(XnDevicePrivateData* pDevicePrivateData); + +XnStatus XnDeviceSensorConfigure(XnDevicePrivateData* pDevicePrivateData); + +XnStatus XnDeviceSensorInitCmosData(XnDevicePrivateData* pDevicePrivateData); + +#endif //_XN_DEVICESENSORINIT_H_ diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.cpp b/Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.cpp new file mode 100644 index 0000000..9ac8b1c --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.cpp @@ -0,0 +1,573 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensorProtocol.h" +#include "XnDeviceSensorIO.h" +#include "Uncomp.h" +#include "XnHostProtocol.h" +#include +#include +#include "XnStreamProcessor.h" +#include "XnSensor.h" +#include + +FILE* g_fUSBDump; + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnBool XN_CALLBACK_TYPE XnDeviceSensorProtocolUsbEpCb(XnUChar* pBuffer, XnUInt32 nBufferSize, void* pCallbackData) +{ + XN_PROFILING_START_MT_SECTION("XnDeviceSensorProtocolUsbEpCb"); + + XnUInt32 nReadBytes; + XnUInt16 nMagic; + + XnSpecificUsbDevice* pDevice = (XnSpecificUsbDevice*)pCallbackData; + XnDevicePrivateData* pDevicePrivateData = pDevice->pDevicePrivateData; + XnUChar* pBufEnd = pBuffer + nBufferSize; + + XnSpecificUsbDeviceState* pCurrState = &pDevice->CurrState; + + while (pBuffer < pBufEnd) + { + switch (pCurrState->State) + { + case XN_WAITING_FOR_CONFIGURATION: + pCurrState->State = XN_IGNORING_GARBAGE; + pCurrState->nMissingBytesInState = pDevice->nIgnoreBytes; + break; + + case XN_IGNORING_GARBAGE: + // ignore first bytes on this endpoint. NOTE: due to a bug in the firmware, the first data received + // on each endpoint is corrupt, causing wrong timestamp calculation, causing future (true) timestamps + // to be calculated wrongly. By ignoring the first data received on each endpoint we hope to get + // only valid data. + nReadBytes = XN_MIN((XnUInt32)(pBufEnd - pBuffer), pCurrState->nMissingBytesInState); + + if (nReadBytes > 0) + { + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "ignoring %d bytes - ignore garbage phase!", nReadBytes); + pCurrState->nMissingBytesInState -= nReadBytes; + pBuffer += nReadBytes; + } + + if (pCurrState->nMissingBytesInState == 0) + { + pCurrState->State = XN_LOOKING_FOR_MAGIC; + pCurrState->nMissingBytesInState = sizeof(XnUInt16); + } + break; + + case XN_LOOKING_FOR_MAGIC: + nMagic = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->FWInfo.nFWMagic); + + if (pCurrState->nMissingBytesInState == sizeof(XnUInt8) && // first byte already found + pBuffer[0] == ((XnUInt8*)&nMagic)[1]) // we have here second byte + { + // move to next byte + pBuffer++; + + // move to next state + pCurrState->CurrHeader.nMagic = nMagic; + pCurrState->State = XN_PACKET_HEADER; + pCurrState->nMissingBytesInState = sizeof(XnSensorProtocolResponseHeader); + break; + } + + while (pBuffer < pBufEnd) + { + if ((pBuffer + sizeof(XnUInt16) <= pBufEnd) && + nMagic == *(XnUInt16*)(pBuffer)) + { + pCurrState->CurrHeader.nMagic = nMagic; + pCurrState->State = XN_PACKET_HEADER; + pCurrState->nMissingBytesInState = sizeof(XnSensorProtocolResponseHeader); + break; + } + else + { + pBuffer++; + } + } + + if (pBuffer == pBufEnd && // magic wasn't found + pBuffer[-1] == ((XnUInt8*)&nMagic)[0]) // last byte in buffer is first in magic + { + // mark that we found first one + pCurrState->nMissingBytesInState--; + } + + break; + + case XN_PACKET_HEADER: + nReadBytes = XN_MIN((XnUInt32)(pBufEnd - pBuffer), pCurrState->nMissingBytesInState); + xnOSMemCopy((XnUChar*)&pCurrState->CurrHeader + sizeof(XnSensorProtocolResponseHeader) - pCurrState->nMissingBytesInState, + pBuffer, nReadBytes); + pCurrState->nMissingBytesInState -= nReadBytes; + pBuffer += nReadBytes; + + if (pCurrState->nMissingBytesInState == 0) + { + // we have entire header. Fix it + pCurrState->CurrHeader.nBufSize = XN_PREPARE_VAR16_IN_BUFFER(pCurrState->CurrHeader.nBufSize); + pCurrState->CurrHeader.nMagic = XN_PREPARE_VAR16_IN_BUFFER(pCurrState->CurrHeader.nMagic); + pCurrState->CurrHeader.nPacketID = XN_PREPARE_VAR16_IN_BUFFER(pCurrState->CurrHeader.nPacketID); + pCurrState->CurrHeader.nTimeStamp = XN_PREPARE_VAR32_IN_BUFFER(pCurrState->CurrHeader.nTimeStamp); + pCurrState->CurrHeader.nType = XN_PREPARE_VAR16_IN_BUFFER(pCurrState->CurrHeader.nType); + pCurrState->CurrHeader.nBufSize = xnOSEndianSwapUINT16(pCurrState->CurrHeader.nBufSize); + pCurrState->CurrHeader.nBufSize -= sizeof(XnSensorProtocolResponseHeader); + + pCurrState->State = XN_PACKET_DATA; + pCurrState->nMissingBytesInState = pCurrState->CurrHeader.nBufSize; + } + break; + + case XN_PACKET_DATA: + nReadBytes = XN_MIN((XnUInt32)(pBufEnd - pBuffer), pCurrState->nMissingBytesInState); + pDevicePrivateData->pSensor->GetFirmware()->GetStreams()->ProcessPacketChunk(&pCurrState->CurrHeader, pBuffer, pCurrState->CurrHeader.nBufSize - pCurrState->nMissingBytesInState, nReadBytes); + pBuffer += nReadBytes; + pCurrState->nMissingBytesInState -= nReadBytes; + + if (pCurrState->nMissingBytesInState == 0) + { + pCurrState->State = XN_LOOKING_FOR_MAGIC; + pCurrState->nMissingBytesInState = sizeof(XnUInt16); + } + break; + } + } + + XN_PROFILING_END_SECTION; + + return TRUE; +} + +XnStatus XnDeviceSensorProtocolFindStreamOfType(XnDevicePrivateData* pDevicePrivateData, const XnChar* strType, const XnChar** ppStreamName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + const XnChar* strNames[100]; + XnUInt32 nCount = 100; + + nRetVal = pDevicePrivateData->pSensor->GetStreamNames(strNames, &nCount); + XN_IS_STATUS_OK(nRetVal); + + for (XnUInt32 i = 0; i < nCount; ++i) + { + XnChar strCurType[XN_DEVICE_MAX_STRING_LENGTH]; + nRetVal = pDevicePrivateData->pSensor->GetProperty(strNames[i], XN_STREAM_PROPERTY_TYPE, strCurType); + XN_IS_STATUS_OK(nRetVal); + + if (strcmp(strType, strCurType) == 0) + { + *ppStreamName = strNames[i]; + return (XN_STATUS_OK); + } + } + + *ppStreamName = NULL; + return (XN_STATUS_NO_MATCH); +} + +XN_THREAD_PROC XnDeviceSensorProtocolScriptThread(XN_THREAD_PARAM pThreadParam) +{ + // Local function variables + XnDevicePrivateData* pDevicePrivateData = (XnDevicePrivateData*)pThreadParam; + XnSensor* pSensor = pDevicePrivateData->pSensor; + + pDevicePrivateData->LogThread.bThreadAlive = TRUE; + pDevicePrivateData->LogThread.bKillThread = FALSE; + + XnStatus rc = XN_STATUS_OK; + + XnUInt32 nVal; + XnUInt32 nLogEvery = 0; + XnStatus ret; + XnI2CWriteData D1; + XnI2CReadData D2; + XnUInt32 i=0; + +// if (FALSE /* pDevicePrivateData->bConfigure*/) + { + XnChar which; + XnUInt32 address, value, mask; + + const XnChar* csFileName = "commands.txt"; + + XnUInt64 nFileSize = 0; + rc = xnOSGetFileSize64(csFileName, &nFileSize); + if (rc != XN_STATUS_OK) + { + XN_THREAD_PROC_RETURN(0); + } + + // read file + XnChar* pcsCommandsFile = (XnChar*)xnOSCalloc((XnSizeT)(nFileSize + 1), sizeof(XnChar)); + rc = xnOSLoadFile(csFileName, pcsCommandsFile, (XnUInt32)nFileSize); + if (rc == XN_STATUS_OK) + { + xnOSSleep(7000); + + XnChar* pFile = pcsCommandsFile; + XnInt32 nRead = 0; + + // Parse commands file + while (*pFile != '\0' && !pDevicePrivateData->LogThread.bKillThread) + { + which = *pFile++; + +// printf("Processing %c\n", which); + switch (which) + { + case ';': + case '#': + // Comment line + while (which != '\n' && *pFile != '\0') + which = *pFile++; + break; + case 'V': + { + XnVersions Version; + + printf("* Requesting Version\n"); + rc = XnHostProtocolGetVersion(pDevicePrivateData, Version); + if (rc == XN_STATUS_OK) + { + printf("** Firmware: V%d.%d.%d; SDK: V%d.%d.%d.%d; Chip: 0x%08x; FPGA: 0x%x; System: 0x%x\n", + Version.nMajor, Version.nMinor, Version.nBuild, + Version.SDK.nMajor, Version.SDK.nMinor, Version.SDK.nMaintenance, Version.SDK.nBuild, + Version.nChip, Version.nFPGA, Version.nSystemVersion); + } + else + { + printf("** GetVersion failed\n"); + } + + while (which != '\n' && *pFile != '\0') + which = *pFile++; + break; + } + case 'L': + { + XnChar LogBuffer[XN_MAX_LOG_SIZE]; + XnHostProtocolGetLog(pDevicePrivateData, LogBuffer, XN_MAX_LOG_SIZE); + printf("%s", (XnChar*)LogBuffer); + break; + } + case 'F': + { + XnUInt32 nFilter; + sscanf(pFile, "%x\n%n", &nFilter, &nRead); + pFile += nRead; + + printf("* LogFilter: 0x%x\n", nFilter); + rc = XnHostProtocolSetParam(pDevicePrivateData, PARAM_MISC_LOG_FILTER, (XnUInt16)nFilter); + if (rc != XN_STATUS_OK) + { + printf("** Set Log Filter failed\n"); + } + else + { + printf("** Done\n"); + } + break; + } + case 'E': + sscanf(pFile, "%u\n%n", &nLogEvery, &nRead); + pFile += nRead; + + if (nLogEvery != 0) + printf("* Will log every %u milliseconds\n", nLogEvery); + + rc = pSensor->SetProperty(XN_MODULE_NAME_DEVICE, XN_MODULE_PROPERTY_FIRMWARE_LOG_INTERVAL, (XnUInt64)nLogEvery); + if (rc != XN_STATUS_OK) + { + printf("** Set log interval failed: %s\n", xnGetStatusString(rc)); + } + + break; + case 'C': + { + XnUInt16 nCMOS; + sscanf(pFile, "%hx %x %x\n%n", &nCMOS, &address, &value, &nRead); + pFile += nRead; + + printf("* Processing '%c 0x%x 0x%x 0x%x'\n", which, nCMOS, address, value); + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_3_0) + { + rc = XnHostProtocolSetCMOSRegisterI2C(pDevicePrivateData, (XnCMOSType)nCMOS, (XnUInt16)address, (XnUInt16)value); + } + else + { + rc = XnHostProtocolSetCMOSRegister(pDevicePrivateData, (XnCMOSType)nCMOS, (XnUInt16)address, (XnUInt16)value); + } + + if (rc != XN_STATUS_OK) + { + printf("** Set CMOS Register failed\n"); + } + else + { + printf("** Done\n"); + } + break; + } + case 'W': + mask = 0; + sscanf(pFile, "%x %x %x\n%n", &address, &value, &mask, &nRead); + pFile += nRead; + + printf("* Processing '%c 0x%x 0x%x 0x%x'\n", which, address, value, mask); + + if (mask == 0) + { + printf("** Bad format?\n"); + continue; + } + + printf("** Sending WriteAHB\n"); + rc = XnHostProtocolWriteAHB(pDevicePrivateData, address, value, mask); + if (rc != XN_STATUS_OK) + { + printf("** Write failed\n"); + } + else + { + printf("** Done\n"); + } + break; + case 'R': + sscanf(pFile, "%x\n%n", &address, &nRead); + pFile += nRead; + + printf("* Processing '%c 0x%x'\n", which , address); + rc = XnHostProtocolReadAHB(pDevicePrivateData, address, value); + if (rc != XN_STATUS_OK) + { + printf("** Read failed\n"); + } + else + { + printf("** AHB[0x%x] = 0x%x\n", address, value); + } + break; + case 'P': + { + XnUInt32 param; + sscanf(pFile, "%u %u\n%n", ¶m, &value, &nRead); + pFile += nRead; + + printf("* Processing '%c %u %u'\n", which , param, value); + rc = XnHostProtocolSetParam(pDevicePrivateData, (XnUInt16)param, (XnUInt16)value); + if (rc != XN_STATUS_OK) + { + printf("** Set Param failed\n"); + } + else + { + printf("** Done\n"); + } + } + break; + case 'U': + { + XnChar filename[80] = {0}; + XnUInt32 nType; + sscanf(pFile, "%u %s\n%n", &nType, filename, &nRead); + pFile += nRead; + + printf("* Processing '%c %u %s'\n", which , nType, filename); + rc = XnHostProtocolFileUpload(pDevicePrivateData, nType, filename, 0); + if (rc != XN_STATUS_OK) + { + printf("** Upload failed\n"); + } + else + { + printf("** Done\n"); + } + } + break; + case 'D': + { + XnChar filename[80] = {0}; + XnUInt32 nType; + sscanf(pFile, "%u %s\n%n", &nType, filename, &nRead); + pFile += nRead; + + printf("* Processing '%c %u %s'\n", which , nType, filename); + rc = XnHostProtocolFileDownload(pDevicePrivateData, (XnUInt16)nType, filename); + if (rc != XN_STATUS_OK) + { + printf("** Download failed\n"); + } + else + { + printf("** Done\n"); + } + } + break; + case 'S': + sscanf(pFile, "%u\n%n", &nVal, &nRead); + pFile += nRead; + + xnOSSleep(nVal); + break; + case 'N': + // Generic I2C Read + sscanf(pFile, "%hx %hx %hx %hx%n", &D2.nBus, &D2.nSlaveAddress, &D2.nReadSize, &D2.nWriteSize, &nRead); + pFile += nRead; + + printf ("* I2C Read: Bus: 0x%hx SlaveAddress: 0x%hx ReadSize: 0x%hx WriteSize: 0x%hx\n", D2.nBus, D2.nSlaveAddress, D2.nReadSize, D2.nWriteSize); + + for (i=0; iLogThread.bThreadAlive = FALSE; + XN_THREAD_PROC_RETURN (XN_STATUS_OK); +} + diff --git a/Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.h b/Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.h new file mode 100644 index 0000000..3c9b9c2 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnDeviceSensorProtocol.h @@ -0,0 +1,134 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_DEVICESENSORPROTOCOL_H_ +#define _XN_DEVICESENSORPROTOCOL_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" +#include "XnHostProtocol.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_SENSOR_PROTOCOL_SENSOR_CLOCK_LENGTH 2 +#define XN_SENSOR_PROTOCOL_SENSOR_VER_LENGTH 1 + +#define XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_START 0x7100 +#define XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_BUFFER 0x7200 +#define XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_END 0x7500 + +#define XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_START 0x8100 +#define XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_BUFFER 0x8200 +#define XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_END 0x8500 + +#define XN_SENSOR_PROTOCOL_RESPONSE_AUDIO_BUFFER 0x9200 + +#define XN_SENSOR_PROTOCOL_RESPONSE_GMC 0xa200 + +#define XN_SENSOR_PROTOCOL_RESPONSE_GMC_DEBUG 0xb200 +#define XN_SENSOR_PROTOCOL_RESPONSE_GMC_DEBUG_END 0xb500 + +#define XN_SENSOR_PROTOCOL_RESPONSE_WAVELENGTH_CORRECTION_DEBUG 0xc200 + +#define XN_SENSOR_PROTOCOL_RESPONSE_TEC_DEBUG 0xd200 + +#define XN_SENSOR_PROTOCOL_RESPONSE_NESA_DEBUG 0xd201 + +#define XN_SENSOR_PROTOCOL_RESPONSE_PROJECTOR_FAULT_EVENT 0xdead + +#define XN_SENSOR_PROTOCOL_RESPONSE_OVERHEAT 0xf31f + +#define XN_SENSOR_PROTOCOL_DEBUG_DATA_EP1 0xdb01 +#define XN_SENSOR_PROTOCOL_DEBUG_DATA_EP2 0xdb02 + +#define XN_SENSOR_PROTOCOL_MAX_BUFFER_SIZE 1024*1024 +#define XN_SENSOR_PROTOCOL_USB_BUFFER_SIZE 4*1024*1024 +#define XN_SENSOR_PROTOCOL_USB_MAX_ZERO_READ_COUNTER 5 +#define XN_SENSOR_PROTOCOL_READ_SLEEP 100 +#define XN_SENSOR_PROTOCOL_READ_MAX_TRIES 30 + +#define XN_SENSOR_PROTOCOL_MAX_RECONFIG_TRIES 10 + +#define XN_SENSOR_PROTOCOL_START_CAPTURE_TRIES 5 +#define XN_SENSOR_PROTOCOL_START_CAPTURE_SLEEP 1000 + +#define XN_SENSOR_PROTOCOL_GMC_MAX_POINTS_IN_PACKET 100 + +/** the number of points to accumulate before processing takes place. */ +#define XN_GMC_MIN_COUNT_FOR_RUNNING 1000 + +//--------------------------------------------------------------------------- +// Structures +//--------------------------------------------------------------------------- +#pragma pack (push, 1) + +typedef struct XnSensorProtocolResponseHeader +{ + XnUInt16 nMagic; + XnUInt16 nType; + XnUInt16 nPacketID; + XnUInt16 nBufSize; + XnUInt32 nTimeStamp; +} XnSensorProtocolResponseHeader; +#pragma pack (pop) // Undo the pack change... + +typedef enum +{ + XN_WAITING_FOR_CONFIGURATION, + XN_IGNORING_GARBAGE, + XN_LOOKING_FOR_MAGIC, + XN_PACKET_HEADER, + XN_PACKET_DATA +} XnMiniPacketState; + +typedef struct XnSpecificUsbDeviceState +{ + XnMiniPacketState State; + XnSensorProtocolResponseHeader CurrHeader; + XnUInt32 nMissingBytesInState; +} XnSpecificUsbDeviceState; + +typedef struct XnSpecificUsbDevice +{ + XnDevicePrivateData* pDevicePrivateData; + XnUsbConnection* pUsbConnection; + XnUInt32 nIgnoreBytes; + XnUInt32 nChunkReadBytes; + XnUInt32 nNumberOfBuffers; + XnSpecificUsbDeviceState CurrState; + XnUInt32 nTimeout; +} XnSpecificUsbDevice; + + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +XnBool XN_CALLBACK_TYPE XnDeviceSensorProtocolUsbEpCb(XnUChar* pBuffer, XnUInt32 nBufferSize, void* pCallbackData); + +XnStatus XnCalculateExpectedImageSize(XnDevicePrivateData* pDevicePrivateData, XnUInt32* pnExpectedSize); +void XnProcessUncompressedDepthPacket(XnSensorProtocolResponseHeader* pCurrHeader, XnUChar* pData, XnUInt32 nDataSize, XnBool bEOP, XnSpecificUsbDevice* pSpecificDevice); +XnStatus XnDeviceSensorProtocolUpdateImageProcessor(XnDevicePrivateData* pDevicePrivateData); + +XN_THREAD_PROC XnDeviceSensorProtocolScriptThread(XN_THREAD_PARAM pThreadParam); + +#endif //_XN_DEVICESENSORPROTOCOL_H_ diff --git a/Source/Drivers/PS1080/Sensor/XnFirmwareCommands.cpp b/Source/Drivers/PS1080/Sensor/XnFirmwareCommands.cpp new file mode 100644 index 0000000..36e8751 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFirmwareCommands.cpp @@ -0,0 +1,48 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFirmwareCommands.h" +#include "XnHostProtocol.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnFirmwareCommands::XnFirmwareCommands(XnDevicePrivateData* pDevicePrivateData) : + m_pDevicePrivateData(pDevicePrivateData) +{ +} + +XnStatus XnFirmwareCommands::GetFirmwareParam(XnUInt16 nParam, XnUInt16* pnValue) +{ + return XnHostProtocolGetParam(m_pDevicePrivateData, nParam, *pnValue); +} + +XnStatus XnFirmwareCommands::SetFirmwareParam(XnUInt16 nParam, XnUInt16 nValue) +{ + return XnHostProtocolSetParam(m_pDevicePrivateData, nParam, nValue); +} + +XnStatus XnFirmwareCommands::SetMultipleFirmwareParams(XnInnerParamData* aParams, XnUInt32 nCount) +{ + return XnHostProtocolSetMultipleParams(m_pDevicePrivateData, (XnUInt16)nCount, aParams); +} diff --git a/Source/Drivers/PS1080/Sensor/XnFirmwareCommands.h b/Source/Drivers/PS1080/Sensor/XnFirmwareCommands.h new file mode 100644 index 0000000..65d6b36 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFirmwareCommands.h @@ -0,0 +1,50 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_FIRMWARE_COMMANDS_H__ +#define __XN_FIRMWARE_COMMANDS_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** +* Implements the various firmware opcodes. +* TODO: all opcodes should move here from XnHostProtocol.cpp. +*/ +class XnFirmwareCommands +{ +public: + XnFirmwareCommands(XnDevicePrivateData* pDevicePrivateData); + + XnStatus GetFirmwareParam(XnUInt16 nParam, XnUInt16* pnValue); + XnStatus SetFirmwareParam(XnUInt16 nParam, XnUInt16 nValue); + XnStatus SetMultipleFirmwareParams(XnInnerParamData* aParams, XnUInt32 nCount); + +private: + XnDevicePrivateData* m_pDevicePrivateData; +}; + +#endif //__XN_FIRMWARE_COMMANDS_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnFirmwareInfo.cpp b/Source/Drivers/PS1080/Sensor/XnFirmwareInfo.cpp new file mode 100644 index 0000000..9784212 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFirmwareInfo.cpp @@ -0,0 +1,24 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFirmwareInfo.h" \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnFirmwareInfo.h b/Source/Drivers/PS1080/Sensor/XnFirmwareInfo.h new file mode 100644 index 0000000..61dc0a1 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFirmwareInfo.h @@ -0,0 +1,125 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_FIRMWARE_INFO_H__ +#define __XN_FIRMWARE_INFO_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnFirmwareInfo +{ +public: + XnFWVer nFWVer; + XnUInt16 nHostMagic; + XnUInt16 nFWMagic; + XnUInt16 nProtocolHeaderSize; + XnUInt16 nProtocolMaxPacketSize; + + XnParamCurrentMode nCurrMode; + + XnBool bAudioSupported; + XnBool bGetPresetsSupported; + XnBool bDeviceInfoSupported; + XnBool bImageAdjustmentsSupported; + + XnUInt16 nOpcodeGetVersion; + XnUInt16 nOpcodeKeepAlive; + XnUInt16 nOpcodeGetParam; + XnUInt16 nOpcodeSetParam; + XnUInt16 nOpcodeGetFixedParams; + XnUInt16 nOpcodeGetMode; + XnUInt16 nOpcodeSetMode; + XnUInt16 nOpcodeAlgorithmParams; + XnUInt16 nOpcodeReset; + XnUInt16 nOpcodeSetCmosBlanking; + XnUInt16 nOpcodeGetCmosBlanking; + XnUInt16 nOpcodeGetCmosPresets; + XnUInt16 nOpcodeGetSerialNumber; + XnUInt16 nOpcodeGetFastConvergenceTEC; + XnUInt16 nOpcodeGetCMOSReg; + XnUInt16 nOpcodeSetCMOSReg; + XnUInt16 nOpcodeWriteI2C; + XnUInt16 nOpcodeReadI2C; + XnUInt16 nOpcodeReadAHB; + XnUInt16 nOpcodeWriteAHB; + XnUInt16 nOpcodeGetPlatformString; + XnUInt16 nOpcodeGetUsbCore; + XnUInt16 nOpcodeSetLedState; + XnUInt16 nOpcodeEnableEmitter; + + XnUInt16 nOpcodeGetLog; + XnUInt16 nOpcodeTakeSnapshot; + XnUInt16 nOpcodeInitFileUpload; + XnUInt16 nOpcodeWriteFileUpload; + XnUInt16 nOpcodeFinishFileUpload; + XnUInt16 nOpcodeDownloadFile; + XnUInt16 nOpcodeDeleteFile; + XnUInt16 nOpcodeGetFlashMap; + XnUInt16 nOpcodeGetFileList; + XnUInt16 nOpcodeSetFileAttribute; + XnUInt16 nOpcodeExecuteFile; + XnUInt16 nOpcodeReadFlash; + XnUInt16 nOpcodeBIST; + XnUInt16 nOpcodeSetGMCParams; + XnUInt16 nOpcodeGetCPUStats; + XnUInt16 nOpcodeCalibrateTec; + XnUInt16 nOpcodeGetTecData; + XnUInt16 nOpcodeCalibrateEmitter; + XnUInt16 nOpcodeGetEmitterData; + XnUInt16 nOpcodeCalibrateProjectorFault; + + XnUInt16 nLogStringType; + XnUInt16 nLogOverflowType; + + XnBool bMirrorSupported; + + XnUInt16 nUSBDelayReceive; + XnUInt16 nUSBDelayExecutePreSend; + XnUInt16 nUSBDelayExecutePostSend; + XnUInt16 nUSBDelaySoftReset; + XnUInt16 nUSBDelaySetParamFlicker; + XnUInt16 nUSBDelaySetParamStream0Mode; + XnUInt16 nUSBDelaySetParamStream1Mode; + XnUInt16 nUSBDelaySetParamStream2Mode; + + XnUInt8 nISOAlternativeInterface; + XnUInt8 nBulkAlternativeInterface; + XnUInt8 nISOLowDepthAlternativeInterface; + + XnBool bGetImageCmosTypeSupported; + XnBool bImageSupported; + XnBool bIncreasedFpsCropSupported; + XnBool bHasFilesystemLock; + + xnl::Array depthModes; + xnl::Array _imageBulkModes; + xnl::Array _imageIsoModes; + xnl::Array imageModes; + xnl::Array irModes; +}; + +#endif //__XN_FIRMWARE_INFO_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnFirmwareStreams.cpp b/Source/Drivers/PS1080/Sensor/XnFirmwareStreams.cpp new file mode 100644 index 0000000..22a5fa4 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFirmwareStreams.cpp @@ -0,0 +1,396 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFirmwareStreams.h" +#include +#include "XnSensor.h" + +#include "XnWavelengthCorrectionDebugProcessor.h" +#include "XnGMCDebugProcessor.h" +#include "XnTecDebugProcessor.h" +#include "XnNesaDebugProcessor.h" +#include "XnGeneralDebugProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnFirmwareStreams::XnFirmwareStreams(XnDevicePrivateData* pDevicePrivateData) : + m_pDevicePrivateData(pDevicePrivateData) +{ +} + +XnFirmwareStreams::~XnFirmwareStreams() +{ +} + +XnStatus XnFirmwareStreams::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFirmwareStreamData tempData; + xnOSMemSet(&tempData, 0, sizeof(XnFirmwareStreamData)); + + // Depth + nRetVal = m_DepthProcessor.Init(); + XN_IS_STATUS_OK(nRetVal); + + tempData.pProcessorHolder = &m_DepthProcessor; + tempData.strType = XN_STREAM_TYPE_DEPTH; + nRetVal = m_FirmwareStreams.Set(XN_STREAM_TYPE_DEPTH, tempData); + XN_IS_STATUS_OK(nRetVal); + + // Image + nRetVal = m_ImageProcessor.Init(); + XN_IS_STATUS_OK(nRetVal); + + tempData.pProcessorHolder = &m_ImageProcessor; + tempData.strType = XN_STREAM_TYPE_IMAGE; + nRetVal = m_FirmwareStreams.Set(XN_STREAM_TYPE_IMAGE, tempData); + XN_IS_STATUS_OK(nRetVal); + + // IR (currently uses the same processor + tempData.pProcessorHolder = &m_ImageProcessor; + tempData.strType = XN_STREAM_TYPE_IR; + nRetVal = m_FirmwareStreams.Set(XN_STREAM_TYPE_IR, tempData); + XN_IS_STATUS_OK(nRetVal); + + // Audio + nRetVal = m_AudioProcessor.Init(); + XN_IS_STATUS_OK(nRetVal); + + tempData.pProcessorHolder = &m_AudioProcessor; + tempData.strType = XN_STREAM_TYPE_AUDIO; + nRetVal = m_FirmwareStreams.Set(XN_STREAM_TYPE_AUDIO, tempData); + XN_IS_STATUS_OK(nRetVal); + + // GMC debug processor + nRetVal = m_GMCDebugProcessor.Init(); + XN_IS_STATUS_OK(nRetVal); + + XnDataProcessor* pProcessor; + XN_VALIDATE_NEW_AND_INIT(pProcessor, XnGMCDebugProcessor, m_pDevicePrivateData); + m_GMCDebugProcessor.Replace(pProcessor); + + // Wave length debug processor + nRetVal = m_WavelengthCorrectionDebugProcessor.Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_NEW_AND_INIT(pProcessor, XnWavelengthCorrectionDebugProcessor, m_pDevicePrivateData); + m_WavelengthCorrectionDebugProcessor.Replace(pProcessor); + + // Tec Debug processor + nRetVal = m_TecDebugProcessor.Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_NEW_AND_INIT(pProcessor, XnTecDebugProcessor, m_pDevicePrivateData); + m_TecDebugProcessor.Replace(pProcessor); + + // NESA Debug processor + nRetVal = m_NesaDebugProcessor.Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_NEW_AND_INIT(pProcessor, XnNesaDebugProcessor, m_pDevicePrivateData); + m_NesaDebugProcessor.Replace(pProcessor); + + // General Debug processors + nRetVal = m_GeneralDebugProcessor1.Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_NEW_AND_INIT(pProcessor, XnGeneralDebugProcessor, m_pDevicePrivateData); + m_GeneralDebugProcessor1.Replace(pProcessor); + + nRetVal = m_GeneralDebugProcessor2.Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_NEW_AND_INIT(pProcessor, XnGeneralDebugProcessor, m_pDevicePrivateData); + m_GeneralDebugProcessor2.Replace(pProcessor); + + return (XN_STATUS_OK); +} + +XnStatus XnFirmwareStreams::CheckClaimStream(const XnChar* strType, XnResolutions nRes, XnUInt32 nFPS, XnDeviceStream* pOwner) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // first of all, make sure this stream isn't claimed already + XnFirmwareStreamData* pStreamData = NULL; + nRetVal = m_FirmwareStreams.Get(strType, pStreamData); + XN_IS_STATUS_OK(nRetVal); + + if (pStreamData->pOwner != NULL && pStreamData->pOwner != pOwner) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Cannot open more than one %s stream at a time!", strType); + } + + if (strcmp(strType, XN_STREAM_TYPE_DEPTH) == 0) + { + // check if IR stream is configured + XnFirmwareStreamData* pIRStreamData = NULL; + nRetVal = m_FirmwareStreams.Get(XN_STREAM_TYPE_IR, pIRStreamData); + XN_IS_STATUS_OK(nRetVal); + + if (pIRStreamData->pOwner != NULL) + { + // check res + if (pIRStreamData->nRes != nRes && (pIRStreamData->nRes != XN_RESOLUTION_SXGA || nRes != XN_RESOLUTION_VGA)) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Cannot set depth stream to resolution %d when IR is set to resolution %d!", nRes, pIRStreamData->nRes); + } + + // check FPS + if (pIRStreamData->nFPS != nFPS) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Depth and IR streams must have the same FPS!"); + } + } + } + else if (strcmp(strType, XN_STREAM_TYPE_IR) == 0) + { + // check if image is configured + XnFirmwareStreamData* pImageStreamData = NULL; + nRetVal = m_FirmwareStreams.Get(XN_STREAM_TYPE_IMAGE, pImageStreamData); + XN_IS_STATUS_OK(nRetVal); + + if (pImageStreamData->pOwner != NULL) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Cannot open IR stream when image stream is on!"); + } + + // check if depth is configured + XnFirmwareStreamData* pDepthStreamData = NULL; + nRetVal = m_FirmwareStreams.Get(XN_STREAM_TYPE_DEPTH, pDepthStreamData); + XN_IS_STATUS_OK(nRetVal); + + if (pDepthStreamData->pOwner != NULL) + { + // check res + if (pDepthStreamData->nRes != nRes && (nRes != XN_RESOLUTION_SXGA || pDepthStreamData->nRes != XN_RESOLUTION_VGA)) + { + if (m_pDevicePrivateData->FWInfo.nFWVer < XN_SENSOR_FW_VER_5_6) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Cannot set IR stream to resolution %d when Depth is set to resolution %d!", nRes, pDepthStreamData->nRes); + } + } + + // check FPS + if (pDepthStreamData->nFPS != nFPS) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Depth and IR streams must have the same FPS!"); + } + } + } + else if (strcmp(strType, XN_STREAM_TYPE_IMAGE) == 0) + { + // check if IR is configured + XnFirmwareStreamData* pIRStreamData; + nRetVal = m_FirmwareStreams.Get(XN_STREAM_TYPE_IR, pIRStreamData); + XN_IS_STATUS_OK(nRetVal); + + if (pIRStreamData->pOwner != NULL) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Cannot open Image stream when IR stream is on!"); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnFirmwareStreams::ClaimStream(const XnChar* strType, XnResolutions nRes, XnUInt32 nFPS, XnDeviceStream* pOwner) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // check constraints + nRetVal = CheckClaimStream(strType, nRes, nFPS, pOwner); + XN_IS_STATUS_OK(nRetVal); + + // get stream data + XnFirmwareStreamData* pData = NULL; + nRetVal = m_FirmwareStreams.Get(strType, pData); + XN_IS_STATUS_OK(nRetVal); + + // update stream + pData->nRes = nRes; + pData->nFPS = nFPS; + pData->pOwner = pOwner; + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "FW Stream %s was claimed by %s", strType, pOwner->GetName()); + + return (XN_STATUS_OK); +} + +XnStatus XnFirmwareStreams::ReleaseStream(const XnChar* strType, XnDeviceStream* pOwner) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get stream data + XnFirmwareStreamData* pData = NULL; + nRetVal = m_FirmwareStreams.Get(strType, pData); + XN_IS_STATUS_OK(nRetVal); + + if (pData->pOwner == NULL || pData->pOwner != pOwner) + { + return XN_STATUS_ERROR; + } + + // release it + pData->pOwner = NULL; + pData->pProcessorHolder->Replace(NULL); + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Stream %s released FW Stream %s", pOwner->GetName(), strType); + + return (XN_STATUS_OK); +} + +XnStatus XnFirmwareStreams::LockStreamProcessor(const XnChar* strType, XnDeviceStream* pOwner) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get stream data + XnFirmwareStreamData* pData = NULL; + nRetVal = m_FirmwareStreams.Get(strType, pData); + XN_IS_STATUS_OK(nRetVal); + + if (pData->pOwner != pOwner) + { + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DEVICE_SENSOR, "Internal error: Trying to lock a processor for a non-owned stream!"); + } + + pData->pProcessorHolder->Lock(); + + return XN_STATUS_OK; +} + +XnStatus XnFirmwareStreams::UnlockStreamProcessor(const XnChar* strType, XnDeviceStream* pOwner) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get stream data + XnFirmwareStreamData* pData = NULL; + nRetVal = m_FirmwareStreams.Get(strType, pData); + XN_IS_STATUS_OK(nRetVal); + + if (pData->pOwner != pOwner) + { + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DEVICE_SENSOR, "Internal error: Trying to unlock a processor for a non-owned stream!"); + } + + pData->pProcessorHolder->Unlock(); + + return XN_STATUS_OK; +} + +XnStatus XnFirmwareStreams::ReplaceStreamProcessor(const XnChar* strType, XnDeviceStream* pOwner, XnDataProcessor* pProcessor) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get stream data + XnFirmwareStreamData* pData = NULL; + nRetVal = m_FirmwareStreams.Get(strType, pData); + XN_IS_STATUS_OK(nRetVal); + + if (pData->pOwner != pOwner) + { + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DEVICE_SENSOR, "Internal error: Trying to replace a processor for a non-owned stream!"); + } + + pData->pProcessorHolder->Replace(pProcessor); + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Firmware stream '%s' processor was replaced.", strType); + + return (XN_STATUS_OK); +} + +XnBool XnFirmwareStreams::IsClaimed(const XnChar* strType, XnDeviceStream* pStream) +{ + XnFirmwareStreamData* pData = NULL; + if (XN_STATUS_OK == m_FirmwareStreams.Get(strType, pData) && pData->pOwner == pStream) + return TRUE; + else + return FALSE; +} + +void XnFirmwareStreams::ProcessPacketChunk(XnSensorProtocolResponseHeader* pHeader, XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + XN_PROFILING_START_MT_SECTION("XnFirmwareStreams::ProcessPacketChunk") + + XnDataProcessorHolder* pStreamProcessor = NULL; + + switch (pHeader->nType) + { + case XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_START: + case XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_BUFFER: + case XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_END: + pStreamProcessor = &m_DepthProcessor; + break; + case XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_START: + case XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_BUFFER: + case XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_END: + pStreamProcessor = &m_ImageProcessor; + break; + case XN_SENSOR_PROTOCOL_RESPONSE_AUDIO_BUFFER: + pStreamProcessor = &m_AudioProcessor; + break; + case XN_SENSOR_PROTOCOL_RESPONSE_GMC_DEBUG: + case XN_SENSOR_PROTOCOL_RESPONSE_GMC_DEBUG_END: + pStreamProcessor = &m_GMCDebugProcessor; + break; + case XN_SENSOR_PROTOCOL_RESPONSE_WAVELENGTH_CORRECTION_DEBUG: + pStreamProcessor = &m_WavelengthCorrectionDebugProcessor; + break; + case XN_SENSOR_PROTOCOL_RESPONSE_TEC_DEBUG: + pStreamProcessor = &m_TecDebugProcessor; + break; + case XN_SENSOR_PROTOCOL_RESPONSE_NESA_DEBUG: + pStreamProcessor = &m_NesaDebugProcessor; + break; + case XN_SENSOR_PROTOCOL_DEBUG_DATA_EP1: + pStreamProcessor = &m_GeneralDebugProcessor1; + break; + case XN_SENSOR_PROTOCOL_DEBUG_DATA_EP2: + pStreamProcessor = &m_GeneralDebugProcessor2; + break; + case XN_SENSOR_PROTOCOL_RESPONSE_PROJECTOR_FAULT_EVENT: + m_pDevicePrivateData->pSensor->SetErrorState(XN_STATUS_DEVICE_PROJECTOR_FAULT); + break; + case XN_SENSOR_PROTOCOL_RESPONSE_OVERHEAT: + m_pDevicePrivateData->pSensor->SetErrorState(XN_STATUS_DEVICE_OVERHEAT); + break; + default: + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "Unknown packet type (0x%x)!!!", pHeader->nType); + } + } + + if (pStreamProcessor != NULL) + { + if (m_pDevicePrivateData->pSensor->GetErrorState() != XN_STATUS_OK) + { + // all OK now. + m_pDevicePrivateData->pSensor->SetErrorState(XN_STATUS_OK); + } + + pStreamProcessor->ProcessData(pHeader, pData, nDataOffset, nDataSize); + } + + XN_PROFILING_END_SECTION +} diff --git a/Source/Drivers/PS1080/Sensor/XnFirmwareStreams.h b/Source/Drivers/PS1080/Sensor/XnFirmwareStreams.h new file mode 100644 index 0000000..816de81 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFirmwareStreams.h @@ -0,0 +1,86 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_FIRMWARE_STREAMS_H__ +#define __XN_FIRMWARE_STREAMS_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDataProcessorHolder.h" +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_STREAM_NAME_GMC "GMC" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnFirmwareStreams +{ +public: + XnFirmwareStreams(XnDevicePrivateData* pDevicePrivateData); + ~XnFirmwareStreams(); + + XnStatus Init(); + + XnStatus ClaimStream(const XnChar* strType, XnResolutions nRes, XnUInt32 nFPS, XnDeviceStream* pOwner); + XnStatus ReleaseStream(const XnChar* strType, XnDeviceStream* pOwner); + XnStatus LockStreamProcessor(const XnChar* strType, XnDeviceStream* pOwner); + XnStatus UnlockStreamProcessor(const XnChar* strType, XnDeviceStream* pOwner); + XnStatus ReplaceStreamProcessor(const XnChar* strType, XnDeviceStream* pOwner, XnDataProcessor* pProcessor); + XnBool IsClaimed(const XnChar* strType, XnDeviceStream* pStream); + + void ProcessPacketChunk(XnSensorProtocolResponseHeader* pHeader, XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + +private: + XnStatus CheckClaimStream(const XnChar* strType, XnResolutions nRes, XnUInt32 nFPS, XnDeviceStream* pOwner); + + XnDevicePrivateData* m_pDevicePrivateData; + + class XnFirmwareStreamData + { + public: + XnDataProcessorHolder* pProcessorHolder; + const XnChar* strType; + XnResolutions nRes; + XnUInt32 nFPS; + XnDeviceStream* pOwner; + }; + + typedef XnStringsHashT XnFirmwareStreamsHash; + + XnFirmwareStreamsHash m_FirmwareStreams; + + XnDataProcessorHolder m_DepthProcessor; + XnDataProcessorHolder m_ImageProcessor; + XnDataProcessorHolder m_AudioProcessor; + XnDataProcessorHolder m_GMCDebugProcessor; + XnDataProcessorHolder m_WavelengthCorrectionDebugProcessor; + XnDataProcessorHolder m_TecDebugProcessor; + XnDataProcessorHolder m_NesaDebugProcessor; + XnDataProcessorHolder m_GeneralDebugProcessor1; + XnDataProcessorHolder m_GeneralDebugProcessor2; +}; + +#endif //__XN_FIRMWARE_STREAMS_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnFirmwareTypes.h b/Source/Drivers/PS1080/Sensor/XnFirmwareTypes.h new file mode 100644 index 0000000..612fd39 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFirmwareTypes.h @@ -0,0 +1,38 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_FIRMWARE_TYPES_H__ +#define __XN_FIRMWARE_TYPES_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef struct +{ + XnUInt32 nTimesExecuted; + XnUInt32 nTimeInMicroSeconds; +} XnTaskCPUInfo; + +#endif //__XN_FIRMWARE_TYPES_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.cpp new file mode 100644 index 0000000..9663e41 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.cpp @@ -0,0 +1,163 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFrameStreamProcessor.h" +#include "XnSensor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnFrameStreamProcessor::XnFrameStreamProcessor(XnFrameStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager, XnUInt16 nTypeSOF, XnUInt16 nTypeEOF) : + XnStreamProcessor(pStream, pHelper), + m_nTypeSOF(nTypeSOF), + m_nTypeEOF(nTypeEOF), + m_pTripleBuffer(pBufferManager), + m_InDump(NULL), + m_InternalDump(NULL), + m_bFrameCorrupted(FALSE), + m_bAllowDoubleSOF(FALSE), + m_nLastSOFPacketID(0), + m_nFirstPacketTimestamp(0) +{ + sprintf(m_csInDumpMask, "%sIn", pStream->GetType()); + sprintf(m_csInternalDumpMask, "Internal%s", pStream->GetType()); + m_InDump = xnDumpFileOpen(m_csInDumpMask, "%s_0.raw", m_csInDumpMask); + m_InternalDump = xnDumpFileOpen(m_csInternalDumpMask, "%s_0.raw", m_csInternalDumpMask); +} + +XnFrameStreamProcessor::~XnFrameStreamProcessor() +{ + xnDumpFileClose(m_InDump); + xnDumpFileClose(m_InternalDump); +} + +void XnFrameStreamProcessor::ProcessPacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnFrameStreamProcessor::ProcessPacketChunk"); + + // if first data from SOF packet + if (pHeader->nType == m_nTypeSOF && nDataOffset == 0) + { + if (!m_bAllowDoubleSOF || pHeader->nPacketID != (m_nLastSOFPacketID + 1)) + { + m_nLastSOFPacketID = pHeader->nPacketID; + OnStartOfFrame(pHeader); + } + } + + if (!m_bFrameCorrupted) + { + xnDumpFileWriteBuffer(m_InDump, pData, nDataSize); + ProcessFramePacketChunk(pHeader, pData, nDataOffset, nDataSize); + } + + // if last data from EOF packet + if (pHeader->nType == m_nTypeEOF && (nDataOffset + nDataSize) == pHeader->nBufSize) + { + OnEndOfFrame(pHeader); + } + + XN_PROFILING_END_SECTION +} + +void XnFrameStreamProcessor::OnPacketLost() +{ + FrameIsCorrupted(); +} + +void XnFrameStreamProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* /*pHeader*/) +{ + m_bFrameCorrupted = FALSE; + m_pTripleBuffer->GetWriteBuffer()->Reset(); + if (m_pDevicePrivateData->pSensor->ShouldUseHostTimestamps()) + { + m_nFirstPacketTimestamp = GetHostTimestamp(); + } +} + +void XnFrameStreamProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + // write dump + XnBuffer* pCurWriteBuffer = m_pTripleBuffer->GetWriteBuffer(); + xnDumpFileWriteBuffer(m_InternalDump, pCurWriteBuffer->GetData(), pCurWriteBuffer->GetSize()); + xnDumpFileClose(m_InternalDump); + xnDumpFileClose(m_InDump); + + if (!m_bFrameCorrupted) + { + // mark the buffer as stable + XnUInt64 nTimestamp; + if (m_pDevicePrivateData->pSensor->ShouldUseHostTimestamps()) + { + // use the host timestamp of the first packet + nTimestamp = m_nFirstPacketTimestamp; + } + else + { + // use timestamp in last packet + nTimestamp = CreateTimestampFromDevice(pHeader->nTimeStamp); + } + + OniFrame* pFrame = m_pTripleBuffer->GetWriteFrame(); + pFrame->timestamp = nTimestamp; + + XnUInt32 nFrameID; + m_pTripleBuffer->MarkWriteBufferAsStable(&nFrameID); + + // let inheriting classes do their stuff + OnFrameReady(nFrameID, nTimestamp); + } + else + { + // restart + m_pTripleBuffer->GetWriteBuffer()->Reset(); + } + + // log bandwidth + XnUInt64 nSysTime; + xnOSGetTimeStamp(&nSysTime); + xnDumpFileWriteString(m_pDevicePrivateData->BandwidthDump, "%llu,%s,%d,%d\n", + nSysTime, m_csName, GetCurrentFrameID(), m_nBytesReceived); + + // re-init dumps + m_InDump = xnDumpFileOpen(m_csInDumpMask, "%s_%d.raw", m_csInDumpMask, GetCurrentFrameID()); + m_InternalDump = xnDumpFileOpen(m_csInternalDumpMask, "%s_%d.raw", m_csInternalDumpMask, GetCurrentFrameID()); + m_nBytesReceived = 0; +} + +void XnFrameStreamProcessor::FrameIsCorrupted() +{ + if (!m_bFrameCorrupted) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "%s frame is corrupt!", m_csName); + m_bFrameCorrupted = TRUE; + } +} + +void XnFrameStreamProcessor::WriteBufferOverflowed() +{ + XnBuffer* pBuffer = GetWriteBuffer(); + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "%s Frame Buffer overflow! current size: %d", m_csName, pBuffer->GetSize()); + FrameIsCorrupted(); +} diff --git a/Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.h b/Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.h new file mode 100644 index 0000000..5a4d715 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnFrameStreamProcessor.h @@ -0,0 +1,183 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_FRAME_STREAM_PROCESSOR_H__ +#define __XN_FRAME_STREAM_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnStreamProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +/* +* A processor for streams that are frame-based. +*/ +class XnFrameStreamProcessor : public XnStreamProcessor +{ +public: + /* + * Initializes a new frame-stream-processor. + * + * @param pDevicePrivateData [in] A pointer to the device. + * @param csName [in] Name of this stream. + * @param nTypeSOF [in] The packet type that signifies start-of-frame. + * @param nTypeEOF [in] The packet type that signifies end-of-frame. + */ + XnFrameStreamProcessor(XnFrameStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager, XnUInt16 nTypeSOF, XnUInt16 nTypeEOF); + + /** + * Destroys a frame-based stream processor + */ + virtual ~XnFrameStreamProcessor(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessPacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnPacketLost(); + + //--------------------------------------------------------------------------- + // New Virtual Functions + //--------------------------------------------------------------------------- + + /* + * Called when a frame starts. + * + * @param pHeader [in] Header for current packet. + */ + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + /* + * Called for every chunk received + * + * @param pHeader [in] A pointer to current packet header. + * @param pData [in] A pointer to the data. + * @param nDataOffset [in] The offset of this data chunk inside current packet. + * @param nDataSize [in] Size of the data in bytes. + */ + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) = 0; + + /* + * Called when a frame ends. + * + * @param pHeader [in] Header for current packet. + */ + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + /* + * Called when a frame is ready for reading. + * + * @param nFrameID [in] ID of this frame. + * @param nFrameTS [in] Timestamp of this frame. + */ + virtual void OnFrameReady(XnUInt32 /*nFrameID*/, XnUInt64 /*nFrameTS*/) {} + + //--------------------------------------------------------------------------- + // Utility Functions + //--------------------------------------------------------------------------- + + inline XnFrameStream* GetStream() + { + return (XnFrameStream*)XnStreamProcessor::GetStream(); + } + + /* + * Gets the expected output size. + */ + inline XnUInt32 GetExpectedOutputSize() + { + return GetStream()->GetRequiredDataSize(); + } + + /* + * Gets current write buffer. + */ + inline XnBuffer* GetWriteBuffer() + { + return m_pTripleBuffer->GetWriteBuffer(); + } + + inline OniFrame* GetWriteFrame() + { + return m_pTripleBuffer->GetWriteFrame(); + } + + /* + * Gets current frame ID (for logging purposes mainly). + */ + inline XnUInt32 GetCurrentFrameID() + { + return m_pTripleBuffer->GetLastFrameID(); + } + + /* + * Notifies that write buffer has overflowed, logging a warning and reseting it. + */ + void WriteBufferOverflowed(); + + /* + * Checks if write buffer has overflowed, if so, a log will be issued and buffer will reset. + */ + inline XnBool CheckWriteBufferForOverflow(XnUInt32 nWriteSize) + { + if (GetWriteBuffer()->GetFreeSpaceInBuffer() < nWriteSize) + { + WriteBufferOverflowed(); + return FALSE; + } + + return TRUE; + } + + /* + * Marks current frame as corrupted. + */ + void FrameIsCorrupted(); + + void SetAllowDoubleSOFPackets(XnBool bAllow) { m_bAllowDoubleSOF = bAllow; } + +private: + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- + /* The type of start-of-frame packet. */ + XnUInt16 m_nTypeSOF; + /* The type of end-of-frame packet. */ + XnUInt16 m_nTypeEOF; + /* A pointer to the triple frame buffer of this stream. */ + XnFrameBufferManager* m_pTripleBuffer; + + XnChar m_csInDumpMask[100]; + XnChar m_csInternalDumpMask[100]; + XnDumpFile* m_InDump; + XnDumpFile* m_InternalDump; + XnBool m_bFrameCorrupted; + XnBool m_bAllowDoubleSOF; + XnUInt16 m_nLastSOFPacketID; + XnUInt64 m_nFirstPacketTimestamp; +}; + +#endif //__XN_FRAME_STREAM_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.cpp new file mode 100644 index 0000000..de1615d --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.cpp @@ -0,0 +1,110 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnGMCDebugProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnGMCDebugProcessor::XnGMCDebugProcessor(XnDevicePrivateData* pDevicePrivateData) : + XnWholePacketProcessor(pDevicePrivateData, "GMCDebug", XN_SENSOR_PROTOCOL_GMC_MAX_POINTS_IN_PACKET*sizeof(XnHostProtocolGMCPoint_1080)), + m_DumpTxt(NULL), + m_DumpBin(NULL), + m_nGMCTime(0) +{ +} + +XnGMCDebugProcessor::~XnGMCDebugProcessor() +{ + xnDumpFileClose(m_DumpTxt); + xnDumpFileClose(m_DumpBin); +} + +void XnGMCDebugProcessor::ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData) +{ + XN_PROFILING_START_SECTION("XnGMCDebugProcessor::ProcessPacketChunk") + + m_DumpTxt = xnDumpFileOpenEx("GMCDebug", TRUE, TRUE, "GMC_Points.%d.txt", m_nGMCTime); + xnDumpFileWriteString(m_DumpTxt, "X,Y,DX,DY\n"); + + if (pHeader->nType == XN_SENSOR_PROTOCOL_RESPONSE_GMC_DEBUG) + { + m_DumpBin = xnDumpFileOpenEx("GMCDebug", TRUE, TRUE, "GMC_Points.%d.xydxdy.bin", m_nGMCTime); + + XnUInt32 nNumOfPoints = pHeader->nBufSize / sizeof(XnHostProtocolGMCPoint_1080); + + // Dump points + for (XnUInt32 nIndex = 0; nIndex < nNumOfPoints; ++nIndex) + { + XnDeviceSensorGMCPoint* pGMCPoint = (XnDeviceSensorGMCPoint*)pData; + + xnDumpFileWriteString(m_DumpTxt, "%d,%d,%d,%d\n", pGMCPoint->m_X, pGMCPoint->m_Y, pGMCPoint->m_DX, pGMCPoint->m_DY); + + XnDouble aDoubles[4]; + aDoubles[0] = pGMCPoint->m_X; + aDoubles[1] = pGMCPoint->m_Y; + aDoubles[2] = pGMCPoint->m_DX; + aDoubles[3] = pGMCPoint->m_DY; + xnDumpFileWriteBuffer(m_DumpBin, aDoubles, sizeof(aDoubles)); + + pData += sizeof(XnHostProtocolGMCPoint_1080); + } + } + else if (pHeader->nType == XN_SENSOR_PROTOCOL_RESPONSE_GMC_DEBUG_END) + { + XnHostProtocolGMCLastPacketData* pGMCLastPacketData = (XnHostProtocolGMCLastPacketData*)pData; + + if (m_pDevicePrivateData->FWInfo.nFWVer < XN_SENSOR_FW_VER_5_2) + { + // we should fill the last field ourselves + pGMCLastPacketData->m_FlashStoredRefOffset = -1000; // not written + } + + xnDumpFileWriteString(m_DumpTxt, "\nMode,%hd\nCoverage Pass:%d\n", pGMCLastPacketData->m_GMCMode,pGMCLastPacketData->m_CoveragePass); + + xnDumpFileWriteString(m_DumpTxt, "Last Configuration:\nN,%hd\nRICC,%hu\nRICC IIR,%f\n\n", + pGMCLastPacketData->m_LastConfData.nLast, pGMCLastPacketData->m_LastConfData.nRICCLast, pGMCLastPacketData->m_LastConfData.fRICC_IIR); + + xnDumpFileWriteString(m_DumpTxt, "New Configuration:\nA,%f\nB,%f\nC,%f\nN,%hd\nRICC,%hu\nStartB,%u\nDeltaB,%u\n", + pGMCLastPacketData->m_A, pGMCLastPacketData->m_B, pGMCLastPacketData->m_C, pGMCLastPacketData->m_N, pGMCLastPacketData->m_RICC, pGMCLastPacketData->m_StartB, pGMCLastPacketData->m_DeltaB); + + if (pGMCLastPacketData->m_FlashStoredRefOffset == -1000) + { + xnDumpFileWriteString(m_DumpTxt, "Flash was not updated."); + } + else + { + xnDumpFileWriteString(m_DumpTxt, "Flash was updated with new reference offset: %hd", pGMCLastPacketData->m_FlashStoredRefOffset); + } + + xnDumpFileClose(m_DumpTxt); + xnDumpFileClose(m_DumpBin); + m_nGMCTime++; + } + + XN_PROFILING_END_SECTION +} + diff --git a/Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.h b/Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.h new file mode 100644 index 0000000..75a87a7 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnGMCDebugProcessor.h @@ -0,0 +1,54 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_GMC_DEBUG_PROCESSOR_H__ +#define __XN_GMC_DEBUG_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnWholePacketProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnGMCDebugProcessor : public XnWholePacketProcessor +{ +public: + XnGMCDebugProcessor(XnDevicePrivateData* pDevicePrivateData); + ~XnGMCDebugProcessor(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnDumpFile* m_DumpTxt; + XnDumpFile* m_DumpBin; + XnUInt32 m_nGMCTime; +}; + +#endif //__XN_GMC_DEBUG_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.cpp new file mode 100644 index 0000000..f79ef3a --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.cpp @@ -0,0 +1,55 @@ +#include "XnGeneralDebugProcessor.h" +#include + +XnGeneralDebugProcessor::XnGeneralDebugProcessor(XnDevicePrivateData* pDevicePrivateData) : XnDataProcessor(pDevicePrivateData, "GeneralDebug"), m_pDump(NULL) +{ + +} + +XnGeneralDebugProcessor::~XnGeneralDebugProcessor() +{ + xnDumpFileClose(m_pDump); +} + +void XnGeneralDebugProcessor::ProcessPacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + if (nDataOffset == 0) + { + // start of data. The first uint16 is the number of fields in the header, and then we have the data itself + const XnUInt16* pFields = (const XnUInt16*)pData; + XnUInt16 nFields = *pFields; + ++pFields; + + XnChar strFileName[XN_FILE_MAX_PATH] = ""; + XnUInt32 nCharsWritten = 0; + XnUInt32 nLength = 0; + xnOSStrFormat(strFileName, XN_FILE_MAX_PATH, &nCharsWritten, "FirmwareDebug."); + nLength += nCharsWritten; + + for (XnUInt16 i = 0; i < nFields; ++i) + { + xnOSStrFormat(strFileName + nLength, XN_FILE_MAX_PATH - nLength, &nCharsWritten, "%02d.", *pFields); + ++pFields; + nLength += nCharsWritten; + } + + xnOSStrFormat(strFileName + nLength, XN_FILE_MAX_PATH - nLength, &nCharsWritten, ".raw"); + + xnDumpFileClose(m_pDump); + m_pDump = xnDumpFileOpenEx("FirmwareDebug", TRUE, TRUE, strFileName); + + const XnUChar* pDataStart = (const XnUChar*)pFields; + nDataSize -= XnUInt32(pDataStart - pData); + pData = pDataStart; + } + + xnDumpFileWriteBuffer(m_pDump, pData, nDataSize); + + if (nDataOffset + nDataSize == pHeader->nBufSize) + { + // end of data + xnDumpFileClose(m_pDump); + m_pDump = NULL; + } +} + diff --git a/Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.h b/Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.h new file mode 100644 index 0000000..9899793 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnGeneralDebugProcessor.h @@ -0,0 +1,39 @@ +/***************************************************************************** +* * +* PrimeSense Sensor 5.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of PrimeSense Sensor * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_GENERAL_DEBUG_PROCESSOR_H__ +#define __XN_GENERAL_DEBUG_PROCESSOR_H__ + +#include "XnDataProcessor.h" + +class XnGeneralDebugProcessor : public XnDataProcessor +{ +public: + XnGeneralDebugProcessor(XnDevicePrivateData* pDevicePrivateData); + ~XnGeneralDebugProcessor(); + +protected: + virtual void ProcessPacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + +private: + XnDumpFile* m_pDump; +}; + +#endif // __XN_GENERAL_DEBUG_PROCESSOR_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnHostProtocol.cpp b/Source/Drivers/PS1080/Sensor/XnHostProtocol.cpp new file mode 100644 index 0000000..6fd4c50 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnHostProtocol.cpp @@ -0,0 +1,3448 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "XnDeviceSensorProtocol.h" +#include "XnHostProtocol.h" +#include +#include +#include "XnSensorDepthStream.h" +#include "XnSensor.h" +#include + +// Control Protocol +#include "XnParams.h" + +#define XN_RECEIVE_USB_DATA_TIMEOUT 20000 +#define XN_USB_HOST_PROTOCOL_TIMEOUT 5000 +#define XN_USB_HOST_PROTOCOL_TIMEOUT_KEEP_ALIVE 5000 +#define XN_USB_HOST_PROTOCOL_TIMEOUT_GETVERSION 5000 +#define XN_USB_HOST_PROTOCOL_TIMEOUT_SETPARAM 5000 + +#define XN_USB_HOST_PROTOCOL_TIMEOUT_UPLOAD 180000 +#define XN_USB_HOST_PROTOCOL_TIMEOUT_FILE_OPS 180000 +#define XN_USB_HOST_PROTOCOL_TIMEOUT_BIST 300000 +#define XN_USB_HOST_PROTOCOL_TIMEOUT_EMITTER_DATA 60000 + +#define XN_USB_HOST_PROTOCOL_FILE_UPLOAD_PRE_DELAY 250 +#define XN_USB_HOST_PROTOCOL_FILE_UPLOAD_POST_DELAY 0 + +#define XN_LOG_TEXT_MESSAGE_V1_2 0x1000 +#define XN_LOG_OVERFLOW_V1_2 0x1001 + +#define XN_LOG_TEXT_MESSAGE_V3_0 0x1200 +#define XN_LOG_OVERFLOW_V3_0 0x1201 + +#define XN_LOG_TEXT_MESSAGE_V4_0 0x1200 +#define XN_LOG_OVERFLOW_V4_0 0x1201 + +#define XN_LOG_TEXT_MESSAGE_V5_0 0x5400 +#define XN_LOG_OVERFLOW_V5_0 0x5401 + +#define XN_USB_HOST_PROTOCOL_SEND_RETRIES 5 +#define XN_HOST_PROTOCOL_NOT_READY_RETRIES 3 + +#define XN_PROTOCOL_MAX_PACKET_SIZE_V5_0 512 +#define XN_PROTOCOL_MAX_PACKET_SIZE_V0_17 64 + +#define MAX_PACKET_SIZE 512 + +inline XnInt32 CompareVersion(XnUInt8 nMajor1, XnUInt8 nMinor1, XnUInt16 nBuild1, XnUInt8 nMajor2, XnUInt8 nMinor2, XnUInt16 nBuild2) +{ + XnInt32 nResult = nMajor1 - nMajor2; + + if (nResult == 0) + { + nResult = nMinor1 - nMinor2; + } + + if (nResult == 0) + { + nResult = nBuild1 - nBuild2; + } + + return (nResult); +} + +static XnFWVer GetFWVersion(XnUInt8 nMajor, XnUInt8 nMinor, XnUInt16 nBuild) +{ + if (CompareVersion(nMajor, nMinor, nBuild, 5, 8, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_8; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 7, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_7; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 6, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_6; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 5, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_5; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 4, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_4; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_3; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 2, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_2; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 1, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_1; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 5, 0, 0) >= 0) + { + return XN_SENSOR_FW_VER_5_0; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 4, 0, 0) >= 0) + { + return XN_SENSOR_FW_VER_4_0; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 3, 0, 0) >= 0) + { + return XN_SENSOR_FW_VER_3_0; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 1, 2, 0) >= 0) + { + return XN_SENSOR_FW_VER_1_2; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 1, 2, 0) >= 0) + { + return XN_SENSOR_FW_VER_1_2; + } + else if (CompareVersion(nMajor, nMinor, nBuild, 1, 1, 0) >= 0) + { + return XN_SENSOR_FW_VER_1_1; + } + else + { + return XN_SENSOR_FW_VER_0_17; + } +} + +XnStatus XnHostProtocolUpdateSupportedImageModes(XnDevicePrivateData* pDevicePrivateData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (pDevicePrivateData->FWInfo.bGetPresetsSupported) + { + // ask the firmware + const XnUInt32 nAllocSize = 100; + XnUInt32 nCount = nAllocSize; + XnCmosPreset aSupportedModes[nAllocSize]; + nRetVal = XnHostProtocolGetCmosPresets(pDevicePrivateData, XN_CMOS_TYPE_IMAGE, aSupportedModes, nCount); + XN_IS_STATUS_OK(nRetVal); + + if (nCount == 0) + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Device does not support any image mode!"); + return XN_STATUS_DEVICE_UNSUPPORTED_PARAMETER; + } + + nRetVal = pDevicePrivateData->FWInfo.imageModes.SetData(aSupportedModes, nCount); + XN_IS_STATUS_OK(nRetVal); + } + else + { + // old firmware. Just use what we know + switch (pDevicePrivateData->pSensor->GetCurrentUsbInterface()) + { + case XN_SENSOR_USB_INTERFACE_BULK_ENDPOINTS: + nRetVal = pDevicePrivateData->FWInfo.imageModes.SetData(pDevicePrivateData->FWInfo._imageBulkModes.GetData(), pDevicePrivateData->FWInfo._imageBulkModes.GetSize()); + XN_IS_STATUS_OK(nRetVal); + break; + case XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS: + nRetVal = pDevicePrivateData->FWInfo.imageModes.SetData(pDevicePrivateData->FWInfo._imageIsoModes.GetData(), pDevicePrivateData->FWInfo._imageIsoModes.GetSize()); + XN_IS_STATUS_OK(nRetVal); + break; + default: + xnLogError(XN_MASK_DEVICE_SENSOR, "Unknown interface in old firmware (%d)", pDevicePrivateData->pSensor->GetCurrentUsbInterface()); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolInitFWParams(XnDevicePrivateData* pDevicePrivateData, XnUInt8 nMajor, XnUInt8 nMinor, XnUInt16 nBuild, XnHostProtocolUsbCore usb, XnBool bGuessed) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // we start with oldest settings (FW version 0.17), and change them for newer versions + pDevicePrivateData->FWInfo.nFWMagic = XN_FW_MAGIC_25; + pDevicePrivateData->FWInfo.nHostMagic = XN_HOST_MAGIC_25; + pDevicePrivateData->FWInfo.nProtocolHeaderSize = sizeof(XnHostProtocolHeaderV25); + pDevicePrivateData->FWInfo.nProtocolMaxPacketSize = XN_PROTOCOL_MAX_PACKET_SIZE_V0_17; + pDevicePrivateData->FWInfo.bAudioSupported = FALSE; + pDevicePrivateData->FWInfo.bMirrorSupported = FALSE; + pDevicePrivateData->FWInfo.bGetPresetsSupported = FALSE; + pDevicePrivateData->FWInfo.bDeviceInfoSupported = FALSE; + pDevicePrivateData->FWInfo.bImageAdjustmentsSupported = FALSE; + + pDevicePrivateData->FWInfo.nOpcodeGetVersion = OPCODE_V017_GET_VERSION; + pDevicePrivateData->FWInfo.nOpcodeKeepAlive = OPCODE_V017_KEEP_ALIVE; + pDevicePrivateData->FWInfo.nOpcodeGetParam = OPCODE_V017_GET_PARAM; + pDevicePrivateData->FWInfo.nOpcodeSetParam = OPCODE_V017_SET_PARAM; + pDevicePrivateData->FWInfo.nOpcodeGetFixedParams = OPCODE_V017_GET_FIXED_PARAMS; + pDevicePrivateData->FWInfo.nOpcodeGetMode = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeSetMode = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeAlgorithmParams = OPCODE_V017_ALGORITM_PARAMS; + pDevicePrivateData->FWInfo.nOpcodeReset = OPCODE_V017_RESET; + pDevicePrivateData->FWInfo.nOpcodeSetCmosBlanking = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCmosBlanking = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCmosPresets = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetSerialNumber = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetFastConvergenceTEC = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCMOSReg = OPCODE_V017_GET_CMOS_REGISTER; + pDevicePrivateData->FWInfo.nOpcodeSetCMOSReg = OPCODE_V017_SET_CMOS_REGISTER; + pDevicePrivateData->FWInfo.nOpcodeWriteI2C = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeReadI2C = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeReadAHB = OPCODE_V017_READ_AHB; + pDevicePrivateData->FWInfo.nOpcodeWriteAHB = OPCODE_V017_WRITE_AHB; + pDevicePrivateData->FWInfo.nOpcodeGetPlatformString = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetUsbCore = OPCODE_GET_USB_CORE_TYPE; + pDevicePrivateData->FWInfo.nOpcodeSetLedState = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeEnableEmitter = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetLog = OPCODE_V017_GET_LOG; + pDevicePrivateData->FWInfo.nOpcodeTakeSnapshot = OPCODE_V017_TAKE_SNAPSHOT; + pDevicePrivateData->FWInfo.nOpcodeInitFileUpload = OPCODE_V017_INIT_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload = OPCODE_V017_WRITE_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeFinishFileUpload = OPCODE_V017_FINISH_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeDownloadFile = OPCODE_V017_DOWNLOAD_FILE; + pDevicePrivateData->FWInfo.nOpcodeDeleteFile = OPCODE_V017_DELETE_FILE; + pDevicePrivateData->FWInfo.nOpcodeGetFlashMap = OPCODE_V017_GET_FLASH_MAP; + pDevicePrivateData->FWInfo.nOpcodeGetFileList = OPCODE_V017_GET_FILE_LIST; + pDevicePrivateData->FWInfo.nOpcodeSetFileAttribute = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeExecuteFile = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeReadFlash = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeBIST = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeSetGMCParams = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCPUStats = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateTec = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetTecData = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateEmitter = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetEmitterData = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateProjectorFault = OPCODE_INVALID; + + pDevicePrivateData->FWInfo.nLogStringType = XN_LOG_TEXT_MESSAGE_V1_2; + pDevicePrivateData->FWInfo.nLogOverflowType = XN_LOG_OVERFLOW_V1_2; + + pDevicePrivateData->FWInfo.nUSBDelayReceive = 100; + pDevicePrivateData->FWInfo.nUSBDelayExecutePreSend = 1; + pDevicePrivateData->FWInfo.nUSBDelayExecutePostSend = 10; + pDevicePrivateData->FWInfo.nUSBDelaySoftReset = 800; + pDevicePrivateData->FWInfo.nUSBDelaySetParamFlicker = 3000; + pDevicePrivateData->FWInfo.nUSBDelaySetParamStream0Mode = 1; + pDevicePrivateData->FWInfo.nUSBDelaySetParamStream1Mode = 300; + pDevicePrivateData->FWInfo.nUSBDelaySetParamStream2Mode = 1; + + pDevicePrivateData->FWInfo.bGetImageCmosTypeSupported = FALSE; + pDevicePrivateData->FWInfo.bImageSupported = TRUE; + pDevicePrivateData->FWInfo.bIncreasedFpsCropSupported = FALSE; + pDevicePrivateData->FWInfo.bHasFilesystemLock = FALSE; + + pDevicePrivateData->FWInfo.nISOLowDepthAlternativeInterface = (XnUInt8)(-1); + + // depth cmos modes + pDevicePrivateData->FWInfo.depthModes.Clear(); + XnCmosPreset depthModes[] = + { + { XN_IO_DEPTH_FORMAT_COMPRESSED_PS, XN_RESOLUTION_QVGA, 30 }, + { XN_IO_DEPTH_FORMAT_COMPRESSED_PS, XN_RESOLUTION_QVGA, 60 }, + { XN_IO_DEPTH_FORMAT_COMPRESSED_PS, XN_RESOLUTION_VGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT, XN_RESOLUTION_QVGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT, XN_RESOLUTION_QVGA, 60 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT, XN_RESOLUTION_VGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT, XN_RESOLUTION_QVGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT, XN_RESOLUTION_QVGA, 60 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT, XN_RESOLUTION_VGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT, XN_RESOLUTION_QVGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT, XN_RESOLUTION_QVGA, 60 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT, XN_RESOLUTION_VGA, 30 }, + }; + nRetVal = pDevicePrivateData->FWInfo.depthModes.AddLast(depthModes, sizeof(depthModes)/sizeof(depthModes[0])); + XN_IS_STATUS_OK(nRetVal); + + // image cmos modes + pDevicePrivateData->FWInfo._imageBulkModes.Clear(); + pDevicePrivateData->FWInfo._imageIsoModes.Clear(); + + XnCmosPreset imageCommonModes[] = + { + { XN_IO_IMAGE_FORMAT_YUV422, XN_RESOLUTION_QVGA, 30 }, + { XN_IO_IMAGE_FORMAT_YUV422, XN_RESOLUTION_QVGA, 60 }, + { XN_IO_IMAGE_FORMAT_YUV422, XN_RESOLUTION_VGA, 30 }, + }; + nRetVal = pDevicePrivateData->FWInfo._imageBulkModes.AddLast(imageCommonModes, sizeof(imageCommonModes)/sizeof(imageCommonModes[0])); + XN_IS_STATUS_OK(nRetVal); + nRetVal = pDevicePrivateData->FWInfo._imageIsoModes.AddLast(imageCommonModes, sizeof(imageCommonModes)/sizeof(imageCommonModes[0])); + XN_IS_STATUS_OK(nRetVal); + + XnCmosPreset imageIsoModes[] = + { + { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_RESOLUTION_QVGA, 30 }, + { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_RESOLUTION_QVGA, 60 }, + { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_RESOLUTION_VGA, 30 }, + }; + nRetVal = pDevicePrivateData->FWInfo._imageIsoModes.AddLast(imageIsoModes, sizeof(imageIsoModes)/sizeof(imageIsoModes[0])); + XN_IS_STATUS_OK(nRetVal); + + // IR cmos modes + pDevicePrivateData->FWInfo.irModes.Clear(); + XnCmosPreset irModes[] = + { + { 0, XN_RESOLUTION_QVGA, 30 }, + { 0, XN_RESOLUTION_QVGA, 60 }, + { 0, XN_RESOLUTION_VGA, 30 }, + }; + nRetVal = pDevicePrivateData->FWInfo.irModes.AddLast(irModes, sizeof(irModes)/sizeof(irModes[0])); + XN_IS_STATUS_OK(nRetVal); + + if (CompareVersion(nMajor, nMinor, nBuild, 1, 1, 0) >= 0) + { + // opcodes were changed + pDevicePrivateData->FWInfo.nOpcodeGetVersion = OPCODE_V110_GET_VERSION; + pDevicePrivateData->FWInfo.nOpcodeKeepAlive = OPCODE_V110_KEEP_ALIVE; + pDevicePrivateData->FWInfo.nOpcodeGetParam = OPCODE_V110_GET_PARAM; + pDevicePrivateData->FWInfo.nOpcodeSetParam = OPCODE_V110_SET_PARAM; + pDevicePrivateData->FWInfo.nOpcodeGetFixedParams = OPCODE_V110_GET_FIXED_PARAMS; + pDevicePrivateData->FWInfo.nOpcodeGetMode = OPCODE_V110_GET_MODE; + pDevicePrivateData->FWInfo.nOpcodeSetMode = OPCODE_V110_SET_MODE; + pDevicePrivateData->FWInfo.nOpcodeAlgorithmParams = OPCODE_V110_ALGORITHM_PARAMS; + pDevicePrivateData->FWInfo.nOpcodeReset = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeSetCmosBlanking = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCmosBlanking = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCmosPresets = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetSerialNumber = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetFastConvergenceTEC = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCMOSReg = OPCODE_V110_GET_CMOS_REGISTER; + pDevicePrivateData->FWInfo.nOpcodeSetCMOSReg = OPCODE_V110_SET_CMOS_REGISTER; + pDevicePrivateData->FWInfo.nOpcodeWriteI2C = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeReadI2C = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeReadAHB = OPCODE_V110_READ_AHB; + pDevicePrivateData->FWInfo.nOpcodeWriteAHB = OPCODE_V110_WRITE_AHB; + pDevicePrivateData->FWInfo.nOpcodeGetLog = OPCODE_V110_GET_LOG; + pDevicePrivateData->FWInfo.nOpcodeTakeSnapshot = OPCODE_V110_TAKE_SNAPSHOT; + pDevicePrivateData->FWInfo.nOpcodeInitFileUpload = OPCODE_V110_INIT_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload = OPCODE_V110_WRITE_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeFinishFileUpload = OPCODE_V110_FINISH_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeDownloadFile = OPCODE_V110_DOWNLOAD_FILE; + pDevicePrivateData->FWInfo.nOpcodeDeleteFile = OPCODE_V110_DELETE_FILE; + pDevicePrivateData->FWInfo.nOpcodeGetFlashMap = OPCODE_V110_GET_FLASH_MAP; + pDevicePrivateData->FWInfo.nOpcodeGetFileList = OPCODE_V110_GET_FILE_LIST; + pDevicePrivateData->FWInfo.nOpcodeSetFileAttribute = OPCODE_V110_SET_FILE_ATTRIBUTES; + pDevicePrivateData->FWInfo.nOpcodeExecuteFile = OPCODE_V110_EXECUTE_FILE; + pDevicePrivateData->FWInfo.nOpcodeReadFlash = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeBIST = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeSetGMCParams = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCPUStats = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateTec = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetTecData = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateEmitter = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetEmitterData = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateProjectorFault = OPCODE_INVALID; + + // log system changed + pDevicePrivateData->FWInfo.nLogStringType = XN_LOG_TEXT_MESSAGE_V1_2; + pDevicePrivateData->FWInfo.nLogOverflowType = XN_LOG_OVERFLOW_V1_2; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 1, 2, 0) >= 0) + { + // protocol header was changed + pDevicePrivateData->FWInfo.nFWMagic = XN_FW_MAGIC_26; + pDevicePrivateData->FWInfo.nHostMagic = XN_HOST_MAGIC_26; + pDevicePrivateData->FWInfo.nProtocolHeaderSize = sizeof(XnHostProtocolHeaderV26); + } + + if (CompareVersion(nMajor, nMinor, nBuild, 3, 0, 0) >= 0) + { + // audio support! + pDevicePrivateData->FWInfo.bAudioSupported = TRUE; + + // opcodes were changed + pDevicePrivateData->FWInfo.nOpcodeGetVersion = OPCODE_GET_VERSION; + pDevicePrivateData->FWInfo.nOpcodeKeepAlive = OPCODE_KEEP_ALIVE; + pDevicePrivateData->FWInfo.nOpcodeGetParam = OPCODE_GET_PARAM; + pDevicePrivateData->FWInfo.nOpcodeSetParam = OPCODE_SET_PARAM; + pDevicePrivateData->FWInfo.nOpcodeGetFixedParams = OPCODE_GET_FIXED_PARAMS; + pDevicePrivateData->FWInfo.nOpcodeGetMode = OPCODE_GET_MODE; + pDevicePrivateData->FWInfo.nOpcodeSetMode = OPCODE_SET_MODE; + pDevicePrivateData->FWInfo.nOpcodeAlgorithmParams = OPCODE_ALGORITM_PARAMS; + pDevicePrivateData->FWInfo.nOpcodeReset = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeSetCmosBlanking = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCmosBlanking = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCmosPresets = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetSerialNumber = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetFastConvergenceTEC = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCMOSReg = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeSetCMOSReg = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeWriteI2C = OPCODE_I2C_WRITE; + pDevicePrivateData->FWInfo.nOpcodeReadI2C = OPCODE_I2C_READ; + pDevicePrivateData->FWInfo.nOpcodeReadAHB = OPCODE_READ_AHB; + pDevicePrivateData->FWInfo.nOpcodeWriteAHB = OPCODE_WRITE_AHB; + pDevicePrivateData->FWInfo.nOpcodeGetLog = OPCODE_GET_LOG; + pDevicePrivateData->FWInfo.nOpcodeTakeSnapshot = OPCODE_TAKE_SNAPSHOT; + pDevicePrivateData->FWInfo.nOpcodeInitFileUpload = OPCODE_INIT_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload = OPCODE_WRITE_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeFinishFileUpload = OPCODE_FINISH_FILE_UPLOAD; + pDevicePrivateData->FWInfo.nOpcodeDownloadFile = OPCODE_DOWNLOAD_FILE; + pDevicePrivateData->FWInfo.nOpcodeDeleteFile = OPCODE_DELETE_FILE; + pDevicePrivateData->FWInfo.nOpcodeGetFlashMap = OPCODE_GET_FLASH_MAP; + pDevicePrivateData->FWInfo.nOpcodeGetFileList = OPCODE_GET_FILE_LIST; + pDevicePrivateData->FWInfo.nOpcodeSetFileAttribute = OPCODE_SET_FILE_ATTRIBUTES; + pDevicePrivateData->FWInfo.nOpcodeExecuteFile = OPCODE_EXECUTE_FILE; + pDevicePrivateData->FWInfo.nOpcodeReadFlash = OPCODE_READ_FLASH; + pDevicePrivateData->FWInfo.nOpcodeBIST = OPCODE_V300_BIST; + pDevicePrivateData->FWInfo.nOpcodeSetGMCParams = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetCPUStats = OPCODE_GET_CPU_STATS; + pDevicePrivateData->FWInfo.nOpcodeCalibrateTec = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetTecData = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateEmitter = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeGetEmitterData = OPCODE_INVALID; + pDevicePrivateData->FWInfo.nOpcodeCalibrateProjectorFault = OPCODE_INVALID; + + // log system changed + pDevicePrivateData->FWInfo.nLogStringType = XN_LOG_TEXT_MESSAGE_V3_0; + pDevicePrivateData->FWInfo.nLogOverflowType = XN_LOG_OVERFLOW_V3_0; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 4, 0, 0) >= 0) + { + // audio removed... + pDevicePrivateData->FWInfo.bAudioSupported = FALSE; + + // opcodes added + pDevicePrivateData->FWInfo.nOpcodeBIST = OPCODE_BIST; + pDevicePrivateData->FWInfo.nOpcodeSetGMCParams = OPCODE_SET_GMC_PARAMS; + + // log system changed + pDevicePrivateData->FWInfo.nLogStringType = XN_LOG_TEXT_MESSAGE_V4_0; + pDevicePrivateData->FWInfo.nLogOverflowType = XN_LOG_OVERFLOW_V4_0; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 0, 0) >= 0) + { + // max packet size changed + pDevicePrivateData->FWInfo.nProtocolMaxPacketSize = XN_PROTOCOL_MAX_PACKET_SIZE_V5_0; + // audio is back on + pDevicePrivateData->FWInfo.bAudioSupported = TRUE; + // mirror supported + pDevicePrivateData->FWInfo.bMirrorSupported = TRUE; + + // opcodes changes + pDevicePrivateData->FWInfo.nOpcodeSetCmosBlanking = OPCODE_SET_CMOS_BLANKING; + pDevicePrivateData->FWInfo.nOpcodeCalibrateTec = OPCODE_CALIBRATE_TEC; + pDevicePrivateData->FWInfo.nOpcodeGetTecData = OPCODE_GET_TEC_DATA; + pDevicePrivateData->FWInfo.nOpcodeCalibrateEmitter = OPCODE_CALIBRATE_EMITTER; + pDevicePrivateData->FWInfo.nOpcodeGetEmitterData = OPCODE_GET_EMITTER_DATA; + pDevicePrivateData->FWInfo.nOpcodeCalibrateProjectorFault = OPCODE_CALIBRATE_PROJECTOR_FAULT; + + // log system changed + pDevicePrivateData->FWInfo.nLogStringType = XN_LOG_TEXT_MESSAGE_V5_0; + pDevicePrivateData->FWInfo.nLogOverflowType = XN_LOG_OVERFLOW_V5_0; + + // ISO endpoints interface was added + pDevicePrivateData->FWInfo.nISOAlternativeInterface = 0; + pDevicePrivateData->FWInfo.nBulkAlternativeInterface = 1; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 1, 0) >= 0) + { + // added high-res IR + XnCmosPreset irHighResMode = { 0, XN_RESOLUTION_SXGA, 30 }; + nRetVal = pDevicePrivateData->FWInfo.irModes.AddLast(irHighResMode); + XN_IS_STATUS_OK(nRetVal); + + // opcode added + pDevicePrivateData->FWInfo.nOpcodeGetCmosBlanking = OPCODE_GET_CMOS_BLANKING; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 2, 0) >= 0 && CompareVersion(nMajor, nMinor, nBuild, 5, 6, 0) < 0) + { + // 25 FPS modes were added + XnCmosPreset depthModes25FPS[] = + { + { XN_IO_DEPTH_FORMAT_COMPRESSED_PS, XN_RESOLUTION_QVGA, 25 }, + { XN_IO_DEPTH_FORMAT_COMPRESSED_PS, XN_RESOLUTION_VGA, 25 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT, XN_RESOLUTION_QVGA, 25 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT, XN_RESOLUTION_VGA, 25 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT, XN_RESOLUTION_QVGA, 25 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT, XN_RESOLUTION_VGA, 25 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT, XN_RESOLUTION_QVGA, 25 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT, XN_RESOLUTION_VGA, 25 }, + }; + nRetVal = pDevicePrivateData->FWInfo.depthModes.AddLast(depthModes25FPS, sizeof(depthModes25FPS)/sizeof(depthModes25FPS[0])); + XN_IS_STATUS_OK(nRetVal); + + XnCmosPreset imageModes25FpsCommon[] = + { + { XN_IO_IMAGE_FORMAT_YUV422, XN_RESOLUTION_QVGA, 25 }, + { XN_IO_IMAGE_FORMAT_YUV422, XN_RESOLUTION_VGA, 25 }, + }; + nRetVal = pDevicePrivateData->FWInfo._imageBulkModes.AddLast(imageModes25FpsCommon, sizeof(imageModes25FpsCommon)/sizeof(imageModes25FpsCommon[0])); + XN_IS_STATUS_OK(nRetVal); + nRetVal = pDevicePrivateData->FWInfo._imageIsoModes.AddLast(imageModes25FpsCommon, sizeof(imageModes25FpsCommon)/sizeof(imageModes25FpsCommon[0])); + XN_IS_STATUS_OK(nRetVal); + + XnCmosPreset imageModes25FpsIso[] = + { + { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_RESOLUTION_QVGA, 25 }, + { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_RESOLUTION_VGA, 25 }, + }; + nRetVal = pDevicePrivateData->FWInfo._imageIsoModes.AddLast(imageModes25FpsIso, sizeof(imageModes25FpsIso)/sizeof(imageModes25FpsIso[0])); + XN_IS_STATUS_OK(nRetVal); + + XnCmosPreset irModes25Fps[] = + { + { 0, XN_RESOLUTION_QVGA, 25 }, + { 0, XN_RESOLUTION_VGA, 25 }, + }; + nRetVal = pDevicePrivateData->FWInfo.irModes.AddLast(irModes25Fps, sizeof(irModes25Fps)/sizeof(irModes25Fps[0])); + XN_IS_STATUS_OK(nRetVal); + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 2, 0) >= 0) + { + // added high-res image modes (UXGA for 5.2, SXGA for 5.3 and newer) + XnCmosPreset imageHighResBayerMode = { XN_IO_IMAGE_FORMAT_BAYER, XN_RESOLUTION_UXGA, 30 }; + if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 0) >= 0) + { + imageHighResBayerMode.nResolution = XN_RESOLUTION_SXGA; + } + nRetVal = pDevicePrivateData->FWInfo._imageBulkModes.AddLast(imageHighResBayerMode); + XN_IS_STATUS_OK(nRetVal); + nRetVal = pDevicePrivateData->FWInfo._imageIsoModes.AddLast(imageHighResBayerMode); + XN_IS_STATUS_OK(nRetVal); + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 15) == 0) + { + pDevicePrivateData->FWInfo.nUSBDelaySetParamFlicker = 300; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 16) >= 0 && + !pDevicePrivateData->pSensor->IsLowBandwidth()) + { + if (usb == XN_USB_CORE_JANGO) + { + pDevicePrivateData->FWInfo.nUSBDelayReceive = 1; + pDevicePrivateData->FWInfo.nUSBDelayExecutePreSend = 0; + pDevicePrivateData->FWInfo.nUSBDelayExecutePostSend = 0; + } + + pDevicePrivateData->FWInfo.nUSBDelaySoftReset = 1; + pDevicePrivateData->FWInfo.nUSBDelaySetParamFlicker = 1; + pDevicePrivateData->FWInfo.nUSBDelaySetParamStream0Mode = 1; + pDevicePrivateData->FWInfo.nUSBDelaySetParamStream1Mode = 1; + pDevicePrivateData->FWInfo.nUSBDelaySetParamStream2Mode = 1; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 25) >= 0) + { + pDevicePrivateData->FWInfo.bDeviceInfoSupported = TRUE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 28) >= 0) + { + // YUV is also supported in high-res + XnCmosPreset imageHighResYuvMode = { XN_IO_IMAGE_FORMAT_YUV422, XN_RESOLUTION_SXGA, 30 }; + nRetVal = pDevicePrivateData->FWInfo._imageBulkModes.AddLast(imageHighResYuvMode); + XN_IS_STATUS_OK(nRetVal); + nRetVal = pDevicePrivateData->FWInfo._imageIsoModes.AddLast(imageHighResYuvMode); + XN_IS_STATUS_OK(nRetVal); + + XnCmosPreset imageHighResYuvModeIso = { XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422, XN_RESOLUTION_SXGA, 30 }; + nRetVal = pDevicePrivateData->FWInfo._imageIsoModes.AddLast(imageHighResYuvModeIso); + XN_IS_STATUS_OK(nRetVal); + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 29) >= 0) + { + pDevicePrivateData->FWInfo.nOpcodeGetCmosPresets = OPCODE_GET_CMOS_PRESETS; + pDevicePrivateData->FWInfo.bGetPresetsSupported = TRUE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 3, 31) >= 0 && CompareVersion(nMajor, nMinor, nBuild, 5, 4, 0) < 0) + { + // file system lock was also added in 5.3.31 (a maintenance release), but it's not in newer versions (5.4 and above) + pDevicePrivateData->FWInfo.bHasFilesystemLock = TRUE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 4, 0) >= 0) + { + pDevicePrivateData->FWInfo.nOpcodeGetSerialNumber = OPCODE_GET_SERIAL_NUMBER; + pDevicePrivateData->FWInfo.nOpcodeGetFastConvergenceTEC = OPCODE_GET_FAST_CONVERGENCE_TEC; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 5, 0) >= 0) + { + // only difference is the interfaces order + pDevicePrivateData->FWInfo.nBulkAlternativeInterface = 0; + pDevicePrivateData->FWInfo.nISOAlternativeInterface = 1; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 6, 0) >= 0) + { + // audio is no longer supported - switched to UAC + pDevicePrivateData->FWInfo.bAudioSupported = FALSE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 6, 2) >= 0) + { + // add QQVGA depth modes + XnCmosPreset aQQmodes[] = + { + { XN_IO_DEPTH_FORMAT_COMPRESSED_PS, XN_RESOLUTION_QQVGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT, XN_RESOLUTION_QQVGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT, XN_RESOLUTION_QQVGA, 30 }, + { XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT, XN_RESOLUTION_QQVGA, 30 }, + }; + nRetVal = pDevicePrivateData->FWInfo.depthModes.AddLast(aQQmodes, sizeof(aQQmodes)/sizeof(aQQmodes[0])); + XN_IS_STATUS_OK(nRetVal); + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 6, 9) >= 0) + { + pDevicePrivateData->FWInfo.bGetImageCmosTypeSupported = TRUE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 7, 0) >= 0) + { + pDevicePrivateData->FWInfo.nOpcodeGetPlatformString = OPCODE_GET_PLATFORM_STRING; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 7, 2) >= 0) + { + pDevicePrivateData->FWInfo.bIncreasedFpsCropSupported = TRUE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 8, 0) >= 0) + { + pDevicePrivateData->FWInfo.nOpcodeSetLedState = OPCODE_SET_LED_STATE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 8, 2) >= 0) + { + pDevicePrivateData->FWInfo.bHasFilesystemLock = TRUE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 8, 9) >= 0) + { + pDevicePrivateData->FWInfo.bImageAdjustmentsSupported = TRUE; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 8, 15) >= 0) + { + pDevicePrivateData->FWInfo.nOpcodeEnableEmitter = OPCODE_ENABLE_EMITTER; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 8, 16) >= 0) + { + pDevicePrivateData->FWInfo.nISOLowDepthAlternativeInterface = 2; + } + + if (CompareVersion(nMajor, nMinor, nBuild, 5, 9, 0) >= 0) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "Sensor version %d.%d.%x is newer than latest known. Trying to use 5.8 protocol...", nMajor, nMinor, nBuild); + } + + // If FW is already known, update image modes + if (!bGuessed) + { + nRetVal = XnHostProtocolUpdateSupportedImageModes(pDevicePrivateData); + XN_IS_STATUS_OK(nRetVal); + } + + pDevicePrivateData->FWInfo.nCurrMode = XN_MODE_PS; + pDevicePrivateData->FWInfo.nFWVer = GetFWVersion(nMajor, nMinor, nBuild); + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolInitHeader(const XnDevicePrivateData* pDevicePrivateData, void* pBuffer, XnUInt32 nSize, XnUInt16 nOpcode) +{ + static XnUInt16 nId = 0; + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_1_2) + { + XnHostProtocolHeaderV26* pHeader = (XnHostProtocolHeaderV26*)pBuffer; + pHeader->nMagic = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->FWInfo.nHostMagic); + pHeader->nSize = XN_PREPARE_VAR16_IN_BUFFER(XnUInt16(nSize/sizeof(XnUInt16))); + pHeader->nOpcode = XN_PREPARE_VAR16_IN_BUFFER(nOpcode); + pHeader->nId = XN_PREPARE_VAR16_IN_BUFFER(nId++); + } + else + { + XnHostProtocolHeaderV25* pHeader = (XnHostProtocolHeaderV25*)pBuffer; + pHeader->nMagic = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->FWInfo.nHostMagic); + pHeader->nSize = XN_PREPARE_VAR16_IN_BUFFER(XnUInt16(nSize/sizeof(XnUInt16))); + pHeader->nOpcode = XN_PREPARE_VAR16_IN_BUFFER(nOpcode); + pHeader->nId = XN_PREPARE_VAR16_IN_BUFFER(nId++); + pHeader->nCRC16 = XN_PREPARE_VAR16_IN_BUFFER(0); + } + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolUSBSend(const XnDevicePrivateData* pDevicePrivateData, + XnUChar* pBuffer, XnUInt16 nSize, XnUInt32 nTimeOut, XnBool bForceBulk) +{ + XnStatus nRetVal = XN_STATUS_OK; + + const XnUsbControlConnection* pCtrlConnection = &pDevicePrivateData->SensorHandle.ControlConnection; + + XnUInt32 nCounter = XN_USB_HOST_PROTOCOL_SEND_RETRIES; + while (nCounter-- != 0) + { + if (pCtrlConnection->bIsBulk || bForceBulk) + nRetVal = xnUSBWriteEndPoint(pCtrlConnection->ControlOutConnectionEp, pBuffer, nSize, nTimeOut); + else + { + nRetVal = xnUSBSendControl(pDevicePrivateData->SensorHandle.USBDevice, XN_USB_CONTROL_TYPE_VENDOR, 0, 0, 0, pBuffer, nSize, nTimeOut); + } + + if (nRetVal != XN_STATUS_USB_TRANSFER_TIMEOUT && nRetVal != XN_STATUS_USB_TRANSFER_STALL) + break; + + xnOSSleep(100); + } + + return nRetVal; +} + +XnStatus XnHostProtocolUSBReceive(const XnDevicePrivateData* pDevicePrivateData, + XnUChar* pBuffer, XnUInt nSize, XnUInt32& nRead, XnUInt32 nTimeOut, XnBool bForceBulk, XnUInt32 nFailTimeout) +{ + XnStatus nRetVal; + XnUInt64 nMaxTime; + XnUInt64 nCurrTime; + + const XnUsbControlConnection* pCtrlConnection = &pDevicePrivateData->SensorHandle.ControlConnection; + + xnOSGetHighResTimeStamp(&nMaxTime); + nMaxTime += (nTimeOut * 1000); + + for (;;) + { + xnOSGetHighResTimeStamp(&nCurrTime); + if (nCurrTime > nMaxTime) + { + return (XN_STATUS_USB_TRANSFER_TIMEOUT); + } + + if (pCtrlConnection->bIsBulk || bForceBulk) + nRetVal = xnUSBReadEndPoint(pCtrlConnection->ControlInConnectionEp, pBuffer, nSize, &nRead, nTimeOut); + else + nRetVal = xnUSBReceiveControl(pDevicePrivateData->SensorHandle.USBDevice, XN_USB_CONTROL_TYPE_VENDOR, 0, 0, 0, pBuffer, nSize, &nRead, nTimeOut); + + if (nRetVal != XN_STATUS_USB_TRANSFER_TIMEOUT && nRetVal != XN_STATUS_USB_TRANSFER_STALL && nRetVal != XN_STATUS_USB_NOT_ENOUGH_DATA) + { + break; + } + + if (nFailTimeout != 0) + { + XnUInt64 nNow; + XnUInt64 nNow2; + xnOSGetHighResTimeStamp(&nNow); + xnOSGetHighResTimeStamp(&nNow2); + while (nNow2 - nNow < nFailTimeout) + { + xnOSGetHighResTimeStamp(&nNow2); + } + } + else + { + xnOSSleep(pDevicePrivateData->FWInfo.nUSBDelayReceive); + } + } + + return nRetVal; +} + +XnStatus ValidateReplyV26(const XnDevicePrivateData* pDevicePrivateData, XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt16 nExpectedOpcode, XnUInt16 nRequestId, XnUInt16& nDataSize, XnUChar** pDataBuf) +{ + XnUInt16 nHeaderOffset = 0; + XnHostProtocolHeaderV26* pHeader = (XnHostProtocolHeaderV26*)pBuffer; + + pHeader->nMagic = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nMagic); + + while (pHeader->nMagic != pDevicePrivateData->FWInfo.nFWMagic && nHeaderOffset < nBufferSize-pDevicePrivateData->FWInfo.nProtocolHeaderSize-sizeof(XnHostProtocolReplyHeader)) + { + nHeaderOffset++; + pHeader = (XnHostProtocolHeaderV26*)(pBuffer+nHeaderOffset); + pHeader->nMagic = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nMagic); + } + + pHeader->nId = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nId); + pHeader->nOpcode = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nOpcode); + pHeader->nSize = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nSize); + + if (pHeader->nMagic != pDevicePrivateData->FWInfo.nFWMagic) + { + return XN_STATUS_DEVICE_PROTOCOL_BAD_MAGIC; + } + + if (pHeader->nId != nRequestId) + { +// printf("Response ID (%d) not the same as request id (%d)\n", pHeader->nId, nRequestId); + return XN_STATUS_DEVICE_PROTOCOL_WRONG_ID; + } + + if (pHeader->nOpcode != nExpectedOpcode) + { +// printf("Unexpected opcode %d (expected: %d)\n", pHeader->nOpcode, nExpectedOpcode); + return XN_STATUS_DEVICE_PROTOCOL_WRONG_OPCODE; + } + // CRC? + // ... + + XnHostProtocolReplyHeader* pReply = (XnHostProtocolReplyHeader*)(pBuffer+nHeaderOffset+pDevicePrivateData->FWInfo.nProtocolHeaderSize); + pReply->nErrorCode = XN_PREPARE_VAR16_IN_BUFFER(pReply->nErrorCode); + + if (pReply->nErrorCode != ACK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "Received NACK: %d", pReply->nErrorCode); + + switch (pReply->nErrorCode) + { + case NACK_INVALID_COMMAND: + return XN_STATUS_DEVICE_PROTOCOL_INVALID_COMMAND; + case NACK_BAD_PACKET_CRC: + return XN_STATUS_DEVICE_PROTOCOL_BAD_PACKET_CRC; + case NACK_BAD_PACKET_SIZE: + return XN_STATUS_DEVICE_PROTOCOL_BAD_PACKET_SIZE; + case NACK_BAD_PARAMS: + return XN_STATUS_DEVICE_PROTOCOL_BAD_PARAMS; + case NACK_I2C_TRANSACTION_FAILED: + return XN_STATUS_DEVICE_PROTOCOL_I2C_TRANSACTION_FAILED; + case NACK_FILE_NOT_FOUND: + return XN_STATUS_DEVICE_PROTOCOL_FILE_NOT_FOUND; + case NACK_FILE_CREATE_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_CREATE_FAILURE; + case NACK_FILE_WRITE_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_WRITE_FAILURE; + case NACK_FILE_DELETE_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_DELETE_FAILURE; + case NACK_FILE_READ_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_READ_FAILURE; + case NACK_BAD_COMMAND_SIZE: + return XN_STATUS_DEVICE_PROTOCOL_BAD_COMMAND_SIZE; + case NACK_NOT_READY: + return XN_STATUS_DEVICE_PROTOCOL_NOT_READY; + case NACK_OVERFLOW: + return XN_STATUS_DEVICE_PROTOCOL_OVERFLOW; + case NACK_OVERLAY_NOT_LOADED: + return XN_STATUS_DEVICE_PROTOCOL_OVERLAY_NOT_LOADED; + case NACK_FILE_SYSTEM_LOCKED: + return XN_STATUS_DEVICE_PROTOCOL_FILE_SYSTEM_LOCKED; + case NACK_UNKNOWN_ERROR: + default: + return XN_STATUS_DEVICE_PROTOCOL_UNKNOWN_ERROR; + } + } + // Check reply length is reasonable for opcode + + nDataSize = pHeader->nSize - sizeof(XnHostProtocolReplyHeader)/sizeof(XnUInt16); + + if (pDataBuf) + *pDataBuf = pBuffer + nHeaderOffset+pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnHostProtocolReplyHeader); + return XN_STATUS_OK; +} + +XnStatus ValidateReplyV25(const XnDevicePrivateData* pDevicePrivateData, XnUChar* pBuffer, XnUInt32 nBufferSize, XnUInt16 nExpectedOpcode, XnUInt16 nRequestId, XnUInt16& nDataSize, XnUChar** pDataBuf) +{ + XnUInt16 nHeaderOffset = 0; + XnHostProtocolHeaderV25* pHeader = (XnHostProtocolHeaderV25*)pBuffer; + + pHeader->nMagic = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nMagic); + + while (pHeader->nMagic != pDevicePrivateData->FWInfo.nFWMagic && nHeaderOffset < nBufferSize-pDevicePrivateData->FWInfo.nProtocolHeaderSize-sizeof(XnHostProtocolReplyHeader)) + { + nHeaderOffset++; + pHeader = (XnHostProtocolHeaderV25*)(pBuffer+nHeaderOffset); + pHeader->nMagic = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nMagic); + } + + pHeader->nCRC16 = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nCRC16); + pHeader->nId = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nId); + pHeader->nOpcode = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nOpcode); + pHeader->nSize = XN_PREPARE_VAR16_IN_BUFFER(pHeader->nSize); + + if (pHeader->nMagic != pDevicePrivateData->FWInfo.nFWMagic) + { + return XN_STATUS_DEVICE_PROTOCOL_BAD_MAGIC; + } + + if (pHeader->nId != nRequestId) + { + // printf("Response ID (%d) not the same as request id (%d)\n", pHeader->nId, nRequestId); + return XN_STATUS_DEVICE_PROTOCOL_WRONG_ID; + } + + if (pHeader->nOpcode != nExpectedOpcode) + { + // printf("Unexpected opcode %d (expected: %d)\n", pHeader->nOpcode, nExpectedOpcode); + return XN_STATUS_DEVICE_PROTOCOL_WRONG_OPCODE; + } + // CRC? + // ... + + XnHostProtocolReplyHeader* pReply = (XnHostProtocolReplyHeader*)(pBuffer+nHeaderOffset+pDevicePrivateData->FWInfo.nProtocolHeaderSize); + + pReply->nErrorCode = XN_PREPARE_VAR16_IN_BUFFER(pReply->nErrorCode); + + if (pReply->nErrorCode != ACK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "Received NACK: %d", pReply->nErrorCode); + + switch (pReply->nErrorCode) + { + case NACK_INVALID_COMMAND: + return XN_STATUS_DEVICE_PROTOCOL_INVALID_COMMAND; + case NACK_BAD_PACKET_CRC: + return XN_STATUS_DEVICE_PROTOCOL_BAD_PACKET_CRC; + case NACK_BAD_PACKET_SIZE: + return XN_STATUS_DEVICE_PROTOCOL_BAD_PACKET_SIZE; + case NACK_BAD_PARAMS: + return XN_STATUS_DEVICE_PROTOCOL_BAD_PARAMS; + case NACK_I2C_TRANSACTION_FAILED: + return XN_STATUS_DEVICE_PROTOCOL_I2C_TRANSACTION_FAILED; + case NACK_FILE_NOT_FOUND: + return XN_STATUS_DEVICE_PROTOCOL_FILE_NOT_FOUND; + case NACK_FILE_CREATE_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_CREATE_FAILURE; + case NACK_FILE_WRITE_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_WRITE_FAILURE; + case NACK_FILE_DELETE_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_DELETE_FAILURE; + case NACK_FILE_READ_FAILURE: + return XN_STATUS_DEVICE_PROTOCOL_FILE_READ_FAILURE; + case NACK_UNKNOWN_ERROR: + default: + return XN_STATUS_DEVICE_PROTOCOL_UNKNOWN_ERROR; + } + } + // Check reply length is reasonable for opcode + + nDataSize = pHeader->nSize - sizeof(XnHostProtocolReplyHeader)/sizeof(XnUInt16); + + if (pDataBuf) + *pDataBuf = pBuffer + nHeaderOffset+pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnHostProtocolReplyHeader); + return XN_STATUS_OK; +} + +XnUInt32 XnHostProtocolGetTimeOut(const XnDevicePrivateData* pDevicePrivateData, XnUInt16 nOpcode) +{ + if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeKeepAlive) + return XN_USB_HOST_PROTOCOL_TIMEOUT_KEEP_ALIVE; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeGetVersion) + return XN_USB_HOST_PROTOCOL_TIMEOUT_GETVERSION; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeSetParam) + return XN_USB_HOST_PROTOCOL_TIMEOUT_SETPARAM; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeInitFileUpload) + return XN_USB_HOST_PROTOCOL_TIMEOUT_UPLOAD; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeDeleteFile) + return XN_USB_HOST_PROTOCOL_TIMEOUT_FILE_OPS; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeSetFileAttribute) + return XN_USB_HOST_PROTOCOL_TIMEOUT_FILE_OPS; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeDownloadFile) + return XN_USB_HOST_PROTOCOL_TIMEOUT_FILE_OPS; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeFinishFileUpload) + return XN_USB_HOST_PROTOCOL_TIMEOUT_FILE_OPS; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload) + return XN_USB_HOST_PROTOCOL_TIMEOUT_FILE_OPS; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeBIST) + return XN_USB_HOST_PROTOCOL_TIMEOUT_BIST; + else if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeGetEmitterData) + return XN_USB_HOST_PROTOCOL_TIMEOUT_EMITTER_DATA; + else + return XN_USB_HOST_PROTOCOL_TIMEOUT; +} + +XnUInt32 XnHostProtocolGetSetParamRecvTimeOut(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nParam) +{ + if (nParam == PARAM_IMAGE_FLICKER_DETECTION) + return pDevicePrivateData->FWInfo.nUSBDelaySetParamFlicker; + else if (nParam == PARAM_GENERAL_STREAM0_MODE) + return pDevicePrivateData->FWInfo.nUSBDelaySetParamStream0Mode; + else if (nParam == PARAM_GENERAL_STREAM1_MODE) + return pDevicePrivateData->FWInfo.nUSBDelaySetParamStream1Mode; + else if (nParam == PARAM_GENERAL_STREAM2_MODE) + return pDevicePrivateData->FWInfo.nUSBDelaySetParamStream2Mode; + else + return 0; +} + +XnStatus XnHostProtocolGetRequestID(const XnDevicePrivateData* pDevicePrivateData, XnUChar* pBuffer, XnUInt16* pnRequestId) +{ + XnUInt16 nRequestId; + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_1_2) + { + nRequestId = ((XnHostProtocolHeaderV26*)(pBuffer))->nId; + } + else + { + nRequestId = ((XnHostProtocolHeaderV25*)(pBuffer))->nId; + } + + *pnRequestId = XN_PREPARE_VAR16_IN_BUFFER(nRequestId); + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolReceiveReply(const XnDevicePrivateData* pDevicePrivateData, XnUChar* pBuffer, XnUInt32 nTimeOut, XnUInt16 nOpcode, XnUInt16 nRequestId, XnUInt32* pnReadBytes, XnUInt16* pnDataSize, XnUChar** ppRelevantBuffer, XnBool bForceBulk, XnUInt32 nRecvTimeout, XnUInt32 nFailTimeout) +{ + XnStatus rc = XN_STATUS_OK; + + XnUInt64 nStartWaitingTime; + xnOSGetTimeStamp(&nStartWaitingTime); + + for (;;) // loop until timeout expires + { + do // loop until right reply ID is received + { + // receive reply + if (nRecvTimeout != 0) + { + xnOSSleep(nRecvTimeout); + } + + rc = XnHostProtocolUSBReceive(pDevicePrivateData, pBuffer, pDevicePrivateData->FWInfo.nProtocolMaxPacketSize, *pnReadBytes, nTimeOut, bForceBulk, nFailTimeout); + XN_IS_STATUS_OK(rc); + + // Validate the reply + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_1_2) + { + rc = ValidateReplyV26(pDevicePrivateData, pBuffer, *pnReadBytes, nOpcode, nRequestId, *pnDataSize, ppRelevantBuffer); + } + else + { + rc = ValidateReplyV25(pDevicePrivateData, pBuffer, *pnReadBytes, nOpcode, nRequestId, *pnDataSize, ppRelevantBuffer); + } + } while (rc == XN_STATUS_DEVICE_PROTOCOL_WRONG_ID); + + XnUInt64 nNow; + xnOSGetTimeStamp(&nNow); + + if (rc != XN_STATUS_OK && rc != XN_STATUS_DEVICE_PROTOCOL_BAD_MAGIC) + { + return rc; + } + else if (rc == XN_STATUS_DEVICE_PROTOCOL_BAD_MAGIC && (nNow-nStartWaitingTime)>XN_RECEIVE_USB_DATA_TIMEOUT) + { + // Timeout expired + return XN_STATUS_DEVICE_PROTOCOL_BAD_MAGIC; + } + else if (rc == XN_STATUS_DEVICE_PROTOCOL_BAD_MAGIC) + { + // Timeout not expired yet + xnOSSleep(10); + } + else + { + // OK + break; + } + } + + return rc; +} + +XnStatus XnHostProtocolExecute(const XnDevicePrivateData* pDevicePrivateData, + XnUChar* pBuffer, XnUInt16 nSize, XnUInt16 nOpcode, + XnUChar** ppRelevantBuffer, XnUInt16& nDataSize, XnUInt32 nRecvTimeout = 0) +{ + XnStatus rc; + XnUInt32 nRead = 0; + XnUInt32 nFailTimeout = 0; + + XnBool bForceBulk = FALSE; + + if (nOpcode == OPCODE_INVALID) + { + return (XN_STATUS_DEVICE_PROTOCOL_UNSUPPORTED_OPCODE); + } + + // don't bother trying to communicate with the device if it was disconnected + if (pDevicePrivateData->pSensor->GetErrorState() == XN_STATUS_DEVICE_NOT_CONNECTED) + { + return (XN_STATUS_DEVICE_NOT_CONNECTED); + } + + XnUInt32 nTimeOut = XnHostProtocolGetTimeOut(pDevicePrivateData, nOpcode); + + // store request (in case we need to retry it) + XnUChar request[MAX_PACKET_SIZE]; + xnOSMemCopy(request, pBuffer, nSize); + + XnUInt16 nRequestId; + rc = XnHostProtocolGetRequestID(pDevicePrivateData, pBuffer, &nRequestId); + XN_IS_STATUS_OK(rc); + + XnUInt16 nRetriesLeft = XN_HOST_PROTOCOL_NOT_READY_RETRIES; + while (nRetriesLeft-- > 0) // loop until device is ready + { + rc = xnOSLockMutex(pDevicePrivateData->hExecuteMutex, XN_WAIT_INFINITE); + XN_IS_STATUS_OK(rc); + + // Sleep before sending the control + if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload) + { + XnUInt64 nNow; + XnUInt64 nNow2; + xnOSGetHighResTimeStamp(&nNow); + xnOSGetHighResTimeStamp(&nNow2); + while (nNow2 - nNow < XN_USB_HOST_PROTOCOL_FILE_UPLOAD_PRE_DELAY) + { + xnOSGetHighResTimeStamp(&nNow2); + } + } + else + { + xnOSSleep(pDevicePrivateData->FWInfo.nUSBDelayExecutePreSend); + } + + // Send request + rc = XnHostProtocolUSBSend(pDevicePrivateData, request, nSize, nTimeOut, bForceBulk); + if (rc != XN_STATUS_OK) + { + xnOSUnLockMutex(pDevicePrivateData->hExecuteMutex); + return rc; + } + + // Sleep before trying to read the reply + if (nOpcode == pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload) + { + nFailTimeout = XN_USB_HOST_PROTOCOL_FILE_UPLOAD_PRE_DELAY; + } + else + { + xnOSSleep(pDevicePrivateData->FWInfo.nUSBDelayExecutePostSend); + } + + // receive reply + rc = XnHostProtocolReceiveReply(pDevicePrivateData, pBuffer, nTimeOut, nOpcode, nRequestId, &nRead, &nDataSize, ppRelevantBuffer, bForceBulk, nRecvTimeout, nFailTimeout); + + if (rc == XN_STATUS_DEVICE_PROTOCOL_NOT_READY || rc == XN_STATUS_OK) + { + XnStatus unlockRC = xnOSUnLockMutex(pDevicePrivateData->hExecuteMutex); + XN_IS_STATUS_OK(unlockRC); + } + else + { + xnOSUnLockMutex(pDevicePrivateData->hExecuteMutex); + return rc; + } + + if (rc == XN_STATUS_OK) + break; + + xnOSSleep(1000); + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Device not ready. %d more retries...", nRetriesLeft); + } + + XN_IS_STATUS_OK(rc); + + if (ppRelevantBuffer == NULL) + return XN_STATUS_OK; + + // Get rest of data + XnInt32 nCur = nRead; // Read so far + + nRead -= (pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnHostProtocolReplyHeader)); // Data read so far + + while (nRead < nDataSize*2U) + { + XnUInt32 dummy = 0; + + rc = XnHostProtocolUSBReceive(pDevicePrivateData, pBuffer+nCur, pDevicePrivateData->FWInfo.nProtocolMaxPacketSize, dummy, nTimeOut, bForceBulk, 0); + if (rc != XN_STATUS_OK) + { + return rc; + } + + nCur += dummy; + nRead += dummy; + } + + return XN_STATUS_OK; +} + +#pragma pack (push, 1) +typedef struct +{ + XnUInt16 nEntrySize; + XnUInt32 nTimeStamp; + XnUInt16 nLogType; +} XnLogEntryHeader; + +typedef struct +{ + XnUInt16 nLine; + XnUInt32 nParam; +} XnLogDefaultData; +#pragma pack(pop) + +XnStatus XnHostProtocolGetLog(XnDevicePrivateData* pDevicePrivateData, XnChar* csBuffer, XnUInt32 nBufferSize) +{ + XnStatus rc = XN_STATUS_OK; + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + XnUChar allLogBuffer[XN_MAX_LOG_SIZE]; + + XnUInt nAllLogBytes = 0; + + // loop until no more log is available + for (;;) + { + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetLog); + + XnUChar* pRelevantBuffer; + XnUInt16 nDataSize; + + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetLog, + &pRelevantBuffer, nDataSize); + + XN_IS_STATUS_OK(rc); + + if (nDataSize == 0) // no more log + break; + + // translate to bytes + nDataSize *= sizeof(XnUInt16); + + if (nAllLogBytes + nDataSize > XN_MAX_LOG_SIZE) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Log Buffer is too small. received %d bytes, but buffer is %d long", nAllLogBytes + nDataSize, XN_MAX_LOG_SIZE); + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + + xnOSMemCopy(allLogBuffer + nAllLogBytes, pRelevantBuffer, nDataSize); + nAllLogBytes += nDataSize; + } + + XnUChar* pCurrData = allLogBuffer; + XnUChar* pEndData = allLogBuffer + nAllLogBytes; + + XnUInt32 nBufferUsed = 0; + + // now parse it + while (pCurrData < pEndData) + { + XnLogEntryHeader* pLogEntryHeader = (XnLogEntryHeader*)pCurrData; + pCurrData += sizeof(XnLogEntryHeader); + + pLogEntryHeader->nEntrySize = XN_PREPARE_VAR16_IN_BUFFER(pLogEntryHeader->nEntrySize); + pLogEntryHeader->nLogType = XN_PREPARE_VAR16_IN_BUFFER(pLogEntryHeader->nLogType); + pLogEntryHeader->nTimeStamp = XN_PREPARE_VAR32_IN_BUFFER(pLogEntryHeader->nTimeStamp); + + // lower byte contains error type and higher contains module ID + XnUInt32 nCharsWritten = 0; + + if (pLogEntryHeader->nLogType == pDevicePrivateData->FWInfo.nLogStringType) + { + // text messages are in wide characters + //XnWChar wcsMessage[600] = {0}; + + if (pLogEntryHeader->nEntrySize*sizeof(XnUInt16) > 600) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Got a log entry with %d bytes!", pLogEntryHeader->nEntrySize*sizeof(XnUInt16)); + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + + rc = xnOSStrFormat((XnChar*)csBuffer + nBufferUsed, nBufferSize - nBufferUsed, &nCharsWritten, "%u:\t", pLogEntryHeader->nTimeStamp); + XN_IS_STATUS_OK(rc); + nBufferUsed += nCharsWritten; + + if (nBufferSize - nBufferUsed < pLogEntryHeader->nEntrySize) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Not enough space in user buffer!"); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + for (XnUInt32 i = 0; i < pLogEntryHeader->nEntrySize; ++i) + { + csBuffer[nBufferUsed++] = pCurrData[i*2]; + } + + csBuffer[nBufferUsed++] = '\n'; + } + else if (pLogEntryHeader->nLogType == pDevicePrivateData->FWInfo.nLogOverflowType) + { + rc = xnOSStrFormat((XnChar*)csBuffer + nBufferUsed, nBufferSize - nBufferUsed, &nCharsWritten, "%u:\tLog Overflow\n", pLogEntryHeader->nTimeStamp); + XN_IS_STATUS_OK(rc); + + nBufferUsed += nCharsWritten; + } + else + { + XnLogDefaultData* pData = (XnLogDefaultData*)pCurrData; + + pData->nLine = XN_PREPARE_VAR16_IN_BUFFER(pData->nLine); + pData->nParam = XN_PREPARE_VAR16_IN_BUFFER(pData->nParam); + + rc = xnOSStrFormat((XnChar*)csBuffer + nBufferUsed, nBufferSize - nBufferUsed, &nCharsWritten, + "%u:\tModule: [0x%X], Error: [0x%X], Param: 0x%X, (Line: %d)\n", + pLogEntryHeader->nTimeStamp, XnChar(pLogEntryHeader->nLogType >> 8), + XnChar(pLogEntryHeader->nLogType), pData->nParam, pData->nLine); + XN_IS_STATUS_OK(rc); + + nBufferUsed += nCharsWritten; + } + + pCurrData += pLogEntryHeader->nEntrySize*sizeof(XnUInt16); + } + + if (nBufferUsed > 0) + { + // add null termination + csBuffer[nBufferUsed] = '\0'; + nBufferUsed++; + } + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetVersion(const XnDevicePrivateData* pDevicePrivateData, XnVersions& Version) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUInt16 nDataSize; + XnVersions *pVersion = NULL; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting hardware versions..."); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetVersion); + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetVersion, + (XnUChar**)(&pVersion), nDataSize); + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Get version failed: %s", xnGetStatusString(rc)); + + return rc; + } + + xnOSMemCopy(&Version, pVersion, sizeof(XnVersions)); + + Version.nBuild = XN_PREPARE_VAR16_IN_BUFFER(Version.nBuild); + Version.nChip = XN_PREPARE_VAR32_IN_BUFFER(Version.nChip); + Version.nFPGA = XN_PREPARE_VAR16_IN_BUFFER(Version.nFPGA); + Version.nSystemVersion = XN_PREPARE_VAR16_IN_BUFFER(Version.nSystemVersion); + + *((XnUInt16*)&Version) = xnOSEndianSwapUINT16(*((XnUInt16*)pVersion)); + + if (Version.nMajor >= 5) + { + XnChar cpBuffer[XN_MAX_OS_NAME_LENGTH]; + sprintf(cpBuffer, "%x", Version.nBuild); + Version.nBuild = (XnUInt16)atoi(cpBuffer); + } + + Version.SDK.nMajor = XN_PS_MAJOR_VERSION; + Version.SDK.nMinor = XN_PS_MINOR_VERSION; + Version.SDK.nMaintenance = XN_PS_MAINTENANCE_VERSION; + Version.SDK.nBuild = XN_PS_BUILD_VERSION; + + // find out hardware version + if (Version.nFPGA == XN_FPGA_VER_FPDB_26) + { + Version.HWVer = XN_SENSOR_HW_VER_FPDB_10; + } + else if (Version.nFPGA == XN_FPGA_VER_FPDB_25) + { + Version.HWVer = XN_SENSOR_HW_VER_FPDB_10; + } + else if (Version.nFPGA == XN_FPGA_VER_CDB) + { + Version.HWVer = XN_SENSOR_HW_VER_CDB_10; + } + else if (Version.nFPGA == XN_FPGA_VER_CDB) + { + Version.HWVer = XN_SENSOR_HW_VER_CDB_10; + } + else if (Version.nFPGA == XN_FPGA_VER_RD3) + { + Version.HWVer = XN_SENSOR_HW_VER_RD_3; + } + else if (Version.nFPGA == XN_FPGA_VER_RD5) + { + Version.HWVer = XN_SENSOR_HW_VER_RD_5; + } + else if (Version.nFPGA == XN_FPGA_VER_RD1081) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1081; + } + else if (Version.nFPGA == XN_FPGA_VER_RD1082) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1082; + } + else if (Version.nFPGA == XN_FPGA_VER_RD109) + { + Version.HWVer = XN_SENSOR_HW_VER_RD109; + } + else + { + Version.HWVer = XN_SENSOR_HW_VER_UNKNOWN; + } + + // find out chip version + if (Version.nChip == XN_CHIP_VER_PS1000) + { + Version.ChipVer = XN_SENSOR_CHIP_VER_PS1000; + } + else if (Version.nChip == XN_CHIP_VER_PS1080) + { + Version.ChipVer = XN_SENSOR_CHIP_VER_PS1080; + } + else if (Version.nChip == XN_CHIP_VER_PS1080A6) + { + Version.ChipVer = XN_SENSOR_CHIP_VER_PS1080A6; + } + else + { + Version.ChipVer = XN_SENSOR_CHIP_VER_UNKNOWN; + } + + // find out sensor version + Version.SensorVer = XN_SENSOR_VER_UNKNOWN; + + // in some firmwares, the HWVer was incorrect. Override according to firmware number + Version.FWVer = GetFWVersion(Version.nMajor, Version.nMinor, Version.nBuild); + if (Version.FWVer == XN_SENSOR_FW_VER_5_0) + { + Version.HWVer = XN_SENSOR_HW_VER_RD_5; + } + else if (Version.FWVer == XN_SENSOR_FW_VER_5_1) + { + Version.HWVer = XN_SENSOR_HW_VER_RD_5; + } + else if (Version.FWVer == XN_SENSOR_FW_VER_5_2) + { + Version.HWVer = XN_SENSOR_HW_VER_RD_5; + } + else if (Version.FWVer == XN_SENSOR_FW_VER_5_3) + { + if (Version.nBuild < 28) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1081; + } + else if (Version.nBuild == 28) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1082; + } + // 5.3.29 and up returns valid HW versions, so no need to override anything + } + else if (Version.FWVer == XN_SENSOR_FW_VER_5_4) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1082; + } + else if (Version.FWVer == XN_SENSOR_FW_VER_5_5) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1082; + } + else if (Version.FWVer == XN_SENSOR_FW_VER_5_6) + { + if (CompareVersion(Version.nMajor, Version.nMinor, Version.nBuild, 5, 6, 6) >= 0) + { + if (Version.nFPGA == 0) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1081; + } + else if (Version.nFPGA == 1) + { + Version.HWVer = XN_SENSOR_HW_VER_RD1082; + } + } + else + { + Version.HWVer = XN_SENSOR_HW_VER_RD1082; + } + } + + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, + "Hardware versions: FW=%d.%d.%d (%d) HW=%d Chip=%d Sensor=%d SYS=%d", + Version.nMajor, Version.nMinor, Version.nBuild, + Version.FWVer, Version.HWVer, Version.ChipVer, + Version.SensorVer, Version.nSystemVersion); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolKeepAlive(XnDevicePrivateData* pDevicePrivateData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Requesting KeepAlive..."); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeKeepAlive); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeKeepAlive, + NULL, nDataSize); + + if (rc == XN_STATUS_OK) + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Got KeepAlive Reply."); + else + xnLogError(XN_MASK_SENSOR_PROTOCOL, "KeepAlive failed: %s", xnGetStatusString(rc)); + + return rc; +} + +XnStatus XnHostProtocolReadAHB(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nAddress, XnUInt32 &nValue) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt32*)pDataBuf = XN_PREPARE_VAR32_IN_BUFFER(nAddress); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt32), pDevicePrivateData->FWInfo.nOpcodeReadAHB); + + XnUInt16 nDataSize; + XnUInt32* pValue = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt32), pDevicePrivateData->FWInfo.nOpcodeReadAHB, + (XnUChar**)(&pValue), nDataSize); + if (rc != XN_STATUS_OK) + { + return rc; + } + + nValue = XN_PREPARE_VAR32_IN_BUFFER(*pValue); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolWriteAHB(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nAddress, XnUInt32 nValue, + XnUInt32 nMask) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Write AHB: 0x%08x 0x%08x 0x%08x", nAddress, nValue, nMask); + + *(XnUInt32*)pDataBuf = XN_PREPARE_VAR32_IN_BUFFER(nAddress); + *(((XnUInt32*)pDataBuf)+1) = XN_PREPARE_VAR32_IN_BUFFER(nValue); + *(((XnUInt32*)pDataBuf)+2) = XN_PREPARE_VAR32_IN_BUFFER(nMask); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt32)*3, pDevicePrivateData->FWInfo.nOpcodeWriteAHB); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt32)*3, pDevicePrivateData->FWInfo.nOpcodeWriteAHB, + NULL, nDataSize); + return rc; +} + +XnStatus XnHostProtocolGetParam(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nParam, XnUInt16& nValue) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + +// xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Getting Parameter [%d]...", nParam); + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nParam); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetParam); + + XnUInt16 nDataSize; + XnUInt16* pValue = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetParam, + (XnUChar**)(&pValue), nDataSize); + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed getting [%d]: %s", nParam, xnGetStatusString(rc)); + return rc; + } + +// xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Param[%d] = %d", nParam, *pValue); + nValue = XN_PREPARE_VAR16_IN_BUFFER(*pValue); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolSetParam(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nParam, XnUInt16 nValue) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + +// xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Setting Parameter [%d] to %d", nParam, nValue); + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nParam); + *(((XnUInt16*)pDataBuf)+1) = XN_PREPARE_VAR16_IN_BUFFER(nValue); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16)*2, pDevicePrivateData->FWInfo.nOpcodeSetParam); + + XnUInt16 nDataSize; + + XnInt32 nTimesLeft = 5; + XnStatus rc = XN_STATUS_ERROR; + while (nTimesLeft > 0) + { + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16)*2, pDevicePrivateData->FWInfo.nOpcodeSetParam, + NULL, nDataSize, XnHostProtocolGetSetParamRecvTimeOut(pDevicePrivateData, nParam)); + nTimesLeft--; + + if (rc == XN_STATUS_OK || + rc == XN_STATUS_DEVICE_PROTOCOL_BAD_PARAMS || + rc == XN_STATUS_DEVICE_NOT_CONNECTED || + rc == XN_STATUS_DEVICE_PROTOCOL_INVALID_COMMAND) + { + break; + } + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Retrying to set the param... rc=%d", rc); + } + + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed setting [%d] to [%d]: %s", nParam, nValue, xnGetStatusString(rc)); + } + else + { +// xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Done."); + } + + return rc; +} + +void XnHostPrototcolAdjustFixedParamsV26(XnFixedParamsV26* pFixedParamsV26, XnFixedParams* pFixedParams) +{ + // the only difference from V2.6 to V3.0 is the 4 last parameters + xnOSMemCopy(pFixedParams, pFixedParamsV26, sizeof(XnFixedParamsV26)); + pFixedParams->nUseExtPhy = pFixedParamsV26->nUseExtPhy; + pFixedParams->bProjectorProtectionEnabled = FALSE; + pFixedParams->nProjectorDACOutputVoltage = FALSE; + pFixedParams->nTecEmitterDelay = pFixedParamsV26->nTecEmitterDelay; +} + +void XnHostPrototcolAdjustFixedParamsV20(XnFixedParamsV20* pFixedParamsV20, XnFixedParams* pFixedParams) +{ + // the only difference from V2.0 to V2.6 is the addition of nUseExtPhy + XnFixedParamsV26 fixedParamsV26; + xnOSMemCopy(&fixedParamsV26, pFixedParamsV20, sizeof(XnFixedParamsV20)); + + // now adjust from V2.6 to current + XnHostPrototcolAdjustFixedParamsV26(&fixedParamsV26, pFixedParams); +} + +XnStatus XnHostProtocolGetFixedParams(XnDevicePrivateData* pDevicePrivateData, XnFixedParams& FixedParams) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUChar* pRelevantBuffer; + XnUInt16 nFixedParamSize = 0; + + XnChar FixedParamsBuffer[2048] = {0}; + XnChar* pData = FixedParamsBuffer; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting the fixed params..."); + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_3_0) + { + nFixedParamSize = sizeof(XnFixedParams); + } + else if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_1_1) + { + nFixedParamSize = sizeof(XnFixedParamsV26); + } + else // v0.17 + { + nFixedParamSize = sizeof(XnFixedParamsV20); + } + + xnOSMemSet(&FixedParams, 0, sizeof(XnFixedParams)); + + XnInt16 nDataRead = 0; + + XnStatus rc; + while (nDataRead < nFixedParamSize) + { + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(XnUInt16(nDataRead/sizeof(XnUInt32))); + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetFixedParams); + + XnUInt16 nDataSize; + + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetFixedParams, + &pRelevantBuffer, nDataSize); + + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Get fixed params failed: %s", xnGetStatusString(rc)); + + return rc; + } + + XnUInt32 nReadNow = nDataSize*sizeof(XnUInt16); + if (nReadNow == 0) + { + break; + } + + xnOSMemCopy(pData + nDataRead, pRelevantBuffer, nReadNow); + + nDataRead += (XnUInt16)nReadNow; + } + + for (XnUInt32 i = 0; i < nFixedParamSize/sizeof(XnUInt32); i ++) + { + XnUInt32 temp = *((XnUInt32*)(&FixedParams)+i); + *((XnUInt32*)(&FixedParams)+i) = XN_PREPARE_VAR32_IN_BUFFER(temp); + } + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_3_0) + { + xnOSMemCopy(&FixedParams, FixedParamsBuffer, sizeof(XnFixedParams)); + } + else if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_1_1) + { + XnFixedParamsV26 fixedParamsV26; + xnOSMemCopy(&fixedParamsV26, FixedParamsBuffer, nFixedParamSize); + XnHostPrototcolAdjustFixedParamsV26(&fixedParamsV26, &FixedParams); + } + else if (pDevicePrivateData->FWInfo.nFWVer == XN_SENSOR_FW_VER_0_17) + { + XnFixedParamsV20 fixedParamsV20; + xnOSMemCopy(&fixedParamsV20, FixedParamsBuffer, nFixedParamSize); + XnHostPrototcolAdjustFixedParamsV20(&fixedParamsV20, &FixedParams); + } + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetMode(XnDevicePrivateData* pDevicePrivateData, XnUInt16& nMode) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetMode); + + XnUInt16 nDataSize; + XnUInt16* pMode = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetMode, + (XnUChar**)(&pMode), nDataSize); + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Get mode failed: %s", xnGetStatusString(rc)); + + return rc; + } + nMode = XN_PREPARE_VAR16_IN_BUFFER(*pMode); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolReset(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nResetType) +{ + XnStatus rc = XN_STATUS_OK; + + if (pDevicePrivateData->FWInfo.nFWVer == XN_SENSOR_FW_VER_0_17) + { + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nResetType); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeReset); + + XnUInt16 nDataSize; + + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeReset, + NULL, nDataSize); + + // Power reset can't fail, and device won't have time to send ACK. + if (nResetType == XN_RESET_TYPE_POWER) + rc = XN_STATUS_OK; + + return rc; + } + else + { + XnUInt16 nActualValue; + switch (nResetType) + { + case XN_RESET_TYPE_POWER: + nActualValue = XN_HOST_PROTOCOL_MODE_REBOOT; + break; + case XN_RESET_TYPE_SOFT: + { + // also kill streams before (in FW < 5.2) + if (pDevicePrivateData->FWInfo.nFWVer < XN_SENSOR_FW_VER_5_2) + { + XnSensorFirmwareParams* pParams = pDevicePrivateData->pSensor->GetFirmware()->GetParams(); + rc = pParams->m_Stream0Mode.SetValue(XN_VIDEO_STREAM_OFF); + XN_IS_STATUS_OK(rc); + rc = pParams->m_Stream1Mode.SetValue(XN_VIDEO_STREAM_OFF); + XN_IS_STATUS_OK(rc); + rc = pParams->m_Stream2Mode.SetValue(XN_AUDIO_STREAM_OFF); + XN_IS_STATUS_OK(rc); + } + } + + nActualValue = XN_HOST_PROTOCOL_MODE_SOFT_RESET; + break; + case XN_RESET_TYPE_SOFT_FIRST: + nActualValue = XN_HOST_PROTOCOL_MODE_SOFT_RESET; + break; + default: + return XN_STATUS_DEVICE_UNSUPPORTED_MODE; + } + + return XnHostProtocolSetMode(pDevicePrivateData, nActualValue); + } +} + +XnStatus XnHostProtocolSetMode(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nMode) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nMode); + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Setting mode to %d...", nMode); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeSetMode); + + XnUInt16 nDataSize; + + XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeSetMode, + NULL, nDataSize); + + // Patch: always return OK when switching modes (since the firmware is changing there is nobody to ACK the request...) + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetCMOSRegister(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, + XnUInt16& nValue) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)nCMOS); + *(((XnUInt16*)pDataBuf)+1) = XN_PREPARE_VAR16_IN_BUFFER(nAddress); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16)*2, pDevicePrivateData->FWInfo.nOpcodeGetCMOSReg); + + XnUInt16 nDataSize; + XnUInt16* pValue = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16)*2, + pDevicePrivateData->FWInfo.nOpcodeGetCMOSReg, (XnUChar**)(&pValue), nDataSize); + if (rc != XN_STATUS_OK) + { + return rc; + } + + nValue = XN_PREPARE_VAR16_IN_BUFFER(*pValue); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolSetCMOSRegister(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, + XnUInt16 nValue) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)nCMOS); + *(((XnUInt16*)pDataBuf)+1) = XN_PREPARE_VAR16_IN_BUFFER(nAddress); + *(((XnUInt16*)pDataBuf)+2) = XN_PREPARE_VAR16_IN_BUFFER(nValue); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16)*3, pDevicePrivateData->FWInfo.nOpcodeSetCMOSReg); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16)*3, + pDevicePrivateData->FWInfo.nOpcodeSetCMOSReg, NULL, nDataSize); + + return rc; +} + +XnStatus XnHostProtocolReadI2C(XnDevicePrivateData* pDevicePrivateData, XnI2CReadData* pI2CReadData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(pI2CReadData->nBus); + *(((XnUInt16*)pDataBuf)+1) = XN_PREPARE_VAR16_IN_BUFFER(pI2CReadData->nSlaveAddress); + *(((XnUInt16*)pDataBuf)+2) = XN_PREPARE_VAR16_IN_BUFFER(pI2CReadData->nReadSize); + for (int i = 0; i < pI2CReadData->nWriteSize; i++) + *((XnUInt16*)pDataBuf+3+i) = XN_PREPARE_VAR16_IN_BUFFER(pI2CReadData->cpWriteBuffer[i]); + + XnUInt16 nOpSize = sizeof(XnUInt16)*3 + (pI2CReadData->nWriteSize * sizeof(XnUInt16)); + XnHostProtocolInitHeader(pDevicePrivateData, buffer, nOpSize, pDevicePrivateData->FWInfo.nOpcodeReadI2C); + + XnUInt16 nDataSize; + XnUInt16* pValue = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+nOpSize, + pDevicePrivateData->FWInfo.nOpcodeReadI2C, (XnUChar**)(&pValue), nDataSize); + + if (rc != XN_STATUS_OK) + { + return rc; + } + + for (int i = 0; i < nDataSize; i++) + pI2CReadData->cpReadBuffer[i] = XN_PREPARE_VAR16_IN_BUFFER(*(pValue+i)); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolWriteI2C(XnDevicePrivateData* pDevicePrivateData, const XnI2CWriteData* pI2CWriteData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(pI2CWriteData->nBus); + *(((XnUInt16*)pDataBuf)+1) = XN_PREPARE_VAR16_IN_BUFFER(pI2CWriteData->nSlaveAddress); + for (int i = 0; i < pI2CWriteData->nWriteSize; i++) + *((XnUInt16*)pDataBuf+2+i) = XN_PREPARE_VAR16_IN_BUFFER(pI2CWriteData->cpWriteBuffer[i]); + + XnUInt16 nOpSize = sizeof(XnUInt16)*2 + (pI2CWriteData->nWriteSize * sizeof(XnUInt16)); + XnHostProtocolInitHeader(pDevicePrivateData, buffer, nOpSize, pDevicePrivateData->FWInfo.nOpcodeWriteI2C); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+nOpSize, + pDevicePrivateData->FWInfo.nOpcodeWriteI2C, NULL, nDataSize); + if (rc != XN_STATUS_OK) + { + return rc; + } + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetCMOSRegisterI2C(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, + XnUInt16& nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnI2CReadData I2CReadData; + + nValue = 0; + I2CReadData.cpReadBuffer[0] = 0; + I2CReadData.cpReadBuffer[1] = 0; + + I2CReadData.nReadSize = XN_PREPARE_VAR16_IN_BUFFER(2); + I2CReadData.nWriteSize = XN_PREPARE_VAR16_IN_BUFFER(1); + I2CReadData.cpWriteBuffer[0]=XN_PREPARE_VAR16_IN_BUFFER(nAddress); + + if (nCMOS == XN_CMOS_TYPE_IMAGE) + { + I2CReadData.nBus = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetImageCmosI2CBus()); + I2CReadData.nSlaveAddress = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetImageCmosI2CSlaveAddress()); + } + else if (nCMOS == XN_CMOS_TYPE_DEPTH) + { + I2CReadData.nBus = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetDepthCmosI2CBus()); + I2CReadData.nSlaveAddress = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetDepthCmosI2CSlaveAddress()); + } + else + { + return (XN_STATUS_ERROR); + } + + nRetVal = XnHostProtocolReadI2C(pDevicePrivateData, &I2CReadData); + XN_IS_STATUS_OK(nRetVal); + + nValue = XN_PREPARE_VAR16_IN_BUFFER((I2CReadData.cpReadBuffer[0] << 8) + I2CReadData.cpReadBuffer[1]); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolSetCMOSRegisterI2C(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, + XnUInt16 nValue) + +{ + XnStatus nRetVal = XN_STATUS_OK; + XnI2CWriteData I2CWriteData; + + I2CWriteData.cpWriteBuffer[0] = XN_PREPARE_VAR16_IN_BUFFER(nAddress); + I2CWriteData.cpWriteBuffer[1] = XN_PREPARE_VAR16_IN_BUFFER((nValue >> 8) & 0xff); + I2CWriteData.cpWriteBuffer[2] = XN_PREPARE_VAR16_IN_BUFFER(nValue & 0xff); + I2CWriteData.nWriteSize = XN_PREPARE_VAR16_IN_BUFFER(3); + + if (nCMOS == XN_CMOS_TYPE_IMAGE) + { + I2CWriteData.nBus = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetImageCmosI2CBus()); + I2CWriteData.nSlaveAddress = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetImageCmosI2CSlaveAddress()); + } + else if (nCMOS == XN_CMOS_TYPE_DEPTH) + { + I2CWriteData.nBus = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetDepthCmosI2CBus()); + I2CWriteData.nSlaveAddress = XN_PREPARE_VAR16_IN_BUFFER(pDevicePrivateData->pSensor->GetFixedParams()->GetDepthCmosI2CSlaveAddress()); + } + else + { + return (XN_STATUS_ERROR); + } + + nRetVal = XnHostProtocolWriteI2C(pDevicePrivateData, &I2CWriteData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolInitUpload(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nOffset, XnUInt16 nAttributes, + XnUInt32 nSizeInWords, XN_FILE_HANDLE &FileToUpload, XnUInt32& nNextOffset) +{ + XnStatus rc = XN_STATUS_OK; + + if (pDevicePrivateData->FWInfo.bHasFilesystemLock) + { + rc = XnHostProtocolSetParam(pDevicePrivateData, PARAM_FILE_SYSTEM_LOCK, 0); + if (rc != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "Failed to unlock file system: %s", xnGetStatusString(rc)); + return rc; + } + } + + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt32*)pDataBuf = XN_PREPARE_VAR32_IN_BUFFER(nOffset); + XnUInt32 *Size = (XnUInt32*)pDataBuf + 1; + *Size = XN_PREPARE_VAR32_IN_BUFFER(nSizeInWords); + XnUInt16 nHeaderSize; + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_1_1) + { + *((XnUInt16*)((XnUInt32*)pDataBuf+2)) = XN_PREPARE_VAR16_IN_BUFFER(nAttributes); + nHeaderSize = sizeof(XnUInt32)+sizeof(XnUInt32)+sizeof(XnUInt16); + } + else + { + nHeaderSize = sizeof(XnUInt32)+sizeof(XnUInt32); + } + + XnUInt32 nReadFromFile = pDevicePrivateData->FWInfo.nProtocolMaxPacketSize - pDevicePrivateData->FWInfo.nProtocolHeaderSize - nHeaderSize; + xnOSSeekFile64(FileToUpload, XN_OS_SEEK_SET, 0); + xnOSReadFile(FileToUpload, pDataBuf + nHeaderSize, &nReadFromFile); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, nHeaderSize+nReadFromFile, pDevicePrivateData->FWInfo.nOpcodeInitFileUpload); + + XnUInt16 nDataSize; + XnUInt32* pValue; + + XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize + nHeaderSize + (XnUInt16)nReadFromFile, + pDevicePrivateData->FWInfo.nOpcodeInitFileUpload, (XnUChar**)(&pValue), nDataSize); + + if (rc != XN_STATUS_OK) + { + return rc; + } + + nNextOffset = XN_PREPARE_VAR32_IN_BUFFER(*pValue); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolWriteUpload(XnDevicePrivateData* pDevicePrivateData, XN_FILE_HANDLE &FileToUpload, + XnUInt32 nOffset, XnUInt32 nFileSize, XnUInt32& nNextOffset) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + XnUInt32 nChunkSize = pDevicePrivateData->FWInfo.nProtocolMaxPacketSize - pDevicePrivateData->FWInfo.nProtocolHeaderSize - sizeof(XnUInt32); + + if (nFileSize-nNextOffset*sizeof(XnUInt16) < nChunkSize) + { + nChunkSize = nFileSize-nNextOffset*sizeof(XnUInt16); + } + + *(XnUInt32*)pDataBuf = XN_PREPARE_VAR32_IN_BUFFER(nOffset); + pDataBuf += sizeof(XnUInt32); + + XnStatus rc = xnOSSeekFile64(FileToUpload, XN_OS_SEEK_SET, nNextOffset*sizeof(XnUInt16)); + XN_IS_STATUS_OK(rc); + + rc = xnOSReadFile(FileToUpload, pDataBuf, &nChunkSize); + XN_IS_STATUS_OK(rc); + + if (nChunkSize == 1) + { + pDataBuf[nChunkSize] = 0; + nChunkSize++; + } + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt32)+nChunkSize, pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload); + + XnUInt16 nDataSize; + XnUInt32* pValue; + + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, (XnUInt16)(pDevicePrivateData->FWInfo.nProtocolHeaderSize + sizeof(XnUInt32) + nChunkSize), + pDevicePrivateData->FWInfo.nOpcodeWriteFileUpload, (XnUChar**)(&pValue), nDataSize); + if (rc != XN_STATUS_OK) + { + return rc; + } + + nNextOffset = XN_PREPARE_VAR32_IN_BUFFER(*pValue); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolFinishUpload (XnDevicePrivateData* pDevicePrivateData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeFinishFileUpload); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, + pDevicePrivateData->FWInfo.nOpcodeFinishFileUpload, NULL, nDataSize); + + return rc; +} + +XnStatus XnHostProtocolFileUpload(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nOffset, + const XnChar* strFileName, XnUInt16 nAttributes) +{ + XnStatus rc; + XnUInt64 nFileSize; + XN_FILE_HANDLE UploadFile; + + rc = xnOSGetFileSize64(strFileName, &nFileSize); + XN_IS_STATUS_OK(rc); + + rc = xnOSOpenFile(strFileName, XN_OS_FILE_READ, &UploadFile); + XN_IS_STATUS_OK(rc); + + if (nFileSize % 2 == 1) + nFileSize++; + + XnUInt32 nNextOffset; + + XnUInt64 nBefore; + xnOSGetTimeStamp(&nBefore); + + rc = XnHostProtocolInitUpload(pDevicePrivateData, nOffset, nAttributes, (XnUInt32)nFileSize/sizeof(XnUInt16), UploadFile, nNextOffset); + if (rc != XN_STATUS_OK) + { + xnOSCloseFile(&UploadFile); + return (rc); + } + + XnUInt64 nNow; + xnOSGetTimeStamp(&nNow); + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Initialized upload of %llu bytes in %llu ms", nFileSize, nNow - nBefore); + + xnOSGetTimeStamp(&nBefore); + + XnUInt32 nLastPrintBytes = 0; + while (nNextOffset*sizeof(XnUInt16) < nFileSize) + { + while ((nNextOffset*sizeof(XnUInt16) - nLastPrintBytes) > 5000) + { + printf("."); + nLastPrintBytes += 5000; + } + + rc = XnHostProtocolWriteUpload(pDevicePrivateData, UploadFile, nNextOffset, (XnUInt32)nFileSize, nNextOffset); + if (rc != XN_STATUS_OK) + { + xnOSCloseFile(&UploadFile); + return (rc); + } + } + printf("\n"); + + xnOSGetTimeStamp(&nNow); + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Uploaded %llu bytes in %llu ms", nFileSize, nNow - nBefore); + + rc = XnHostProtocolFinishUpload(pDevicePrivateData); + if (rc != XN_STATUS_OK) + { + xnOSCloseFile(&UploadFile); + return (rc); + } + + xnOSCloseFile(&UploadFile); + + return rc; +} + +XnStatus XnHostProtocolDeleteFile(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileId) +{ + XnStatus rc = XN_STATUS_OK; + + if (pDevicePrivateData->FWInfo.bHasFilesystemLock) + { + rc = XnHostProtocolSetParam(pDevicePrivateData, PARAM_FILE_SYSTEM_LOCK, 0); + if (rc != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "Failed to unlock file system: %s", xnGetStatusString(rc)); + return rc; + } + } + + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nFileId); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeDeleteFile); + + XnUInt16 nDataSize; + + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeDeleteFile, + NULL, nDataSize); + + return rc; +} + +XnStatus XnHostProtocolSetFileAttributes(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileId, XnUInt16 nAttributes) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nFileId); + *(((XnUInt16*)pDataBuf)+1) = XN_PREPARE_VAR16_IN_BUFFER(nAttributes); + + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 2*sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeSetFileAttribute); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+2*sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeSetFileAttribute, + NULL, nDataSize); + + return rc; +} + +XnStatus XnHostProtocolExecuteFile(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileId) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nFileId); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeExecuteFile); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeExecuteFile, + NULL, nDataSize); + + return rc; +} + +XnStatus XnHostProtocolGetFlashMap(XnDevicePrivateData* pDevicePrivateData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetFlashMap); + + XnUInt nRead; + XnUChar* pRelevantBuffer; + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetFlashMap, + &pRelevantBuffer, nDataSize); + if (rc != XN_STATUS_OK) + { +// printf("Get Flash MapExecution failed\n"); + return rc; + } + + // Deal with specific log reply +#pragma pack(push, 1) + typedef struct + { + XnUInt16 nFileType; + XnUInt32 nOffsetInFlash; + XnUInt32 nSizeInWords; + struct + { + XnUInt8 nMajor; + XnUInt8 nMinor; + XnUInt16 nBuild; + } Version; + } XnFlashEntry; +#pragma pack (pop) + + nRead = pDevicePrivateData->FWInfo.nProtocolHeaderSize+nDataSize*2; + XnFlashEntry* pFlashEntry; + + // Go over log and print it out + while (pRelevantBuffer < buffer + nRead) + { + pFlashEntry = (XnFlashEntry*)pRelevantBuffer; + + pFlashEntry->nFileType = XN_PREPARE_VAR16_IN_BUFFER(pFlashEntry->nFileType); + pFlashEntry->nOffsetInFlash = XN_PREPARE_VAR32_IN_BUFFER(pFlashEntry->nOffsetInFlash); + pFlashEntry->nSizeInWords = XN_PREPARE_VAR32_IN_BUFFER(pFlashEntry->nSizeInWords); + pFlashEntry->Version.nBuild = XN_PREPARE_VAR16_IN_BUFFER(pFlashEntry->Version.nBuild); + + printf("File Type: %d\n", pFlashEntry->nFileType); + printf("Offset: %u\n", pFlashEntry->nOffsetInFlash); + printf("Size in Words: %u\n", pFlashEntry->nSizeInWords); + printf("Version: %d.%d.%d\n", pFlashEntry->Version.nMajor, pFlashEntry->Version.nMinor, + pFlashEntry->Version.nBuild); + + pRelevantBuffer += sizeof(XnFlashEntry); + } + + return XN_STATUS_OK; +} + +#pragma pack (push, 1) +typedef struct XnAlgorithmParamRequest +{ + XnUInt16 nParamID; + XnUInt16 nFormat; + XnUInt16 nResolution; + XnUInt16 nFPS; + XnUInt16 nOffset; +} XnAlgorithmParamRequest; + +typedef struct XnAlgorithmParamRequestV4 +{ + XnUInt8 nResolution; + XnUInt8 nFPS; + XnUInt8 nFormat; + XnUInt8 nParamID; + XnUInt16 nOffset; +} XnAlgorithmParamRequestV4; +#pragma pack (pop) + +XnStatus XnHostProtocolAlgorithmParams(XnDevicePrivateData* pDevicePrivateData, + XnHostProtocolAlgorithmType eAlgorithmType, + void* pAlgorithmInformation, XnUInt16 nAlgInfoSize, XnResolutions nResolution, XnUInt16 nFPS) +{ + XnChar* pData = (XnChar*)pAlgorithmInformation; + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUChar* pRelevantBuffer; + + XnInt16 nDataRead = 0; + XnUInt16 nRequestSize = 0; + + if (eAlgorithmType == XN_HOST_PROTOCOL_ALGORITHM_DEVICE_INFO && + !pDevicePrivateData->FWInfo.bDeviceInfoSupported) + { + XnDeviceInformation* pDeviceInfo = (XnDeviceInformation*)pAlgorithmInformation; + strcpy(pDeviceInfo->strDeviceName, "PrimeSense Sensor"); + strcpy(pDeviceInfo->strVendorData, ""); + return XN_STATUS_OK; + } + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting algorithm params 0x%x for resolution %d and fps %d....", eAlgorithmType, nResolution, nFPS); + + XnStatus rc; + while (nDataRead < nAlgInfoSize) + { + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_1) + { + XnAlgorithmParamRequest* pRequest = (XnAlgorithmParamRequest*)pDataBuf; + pRequest->nParamID = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)eAlgorithmType); + pRequest->nFormat = 0; + pRequest->nResolution = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)nResolution); + pRequest->nFPS = XN_PREPARE_VAR16_IN_BUFFER(nFPS); + pRequest->nOffset = XN_PREPARE_VAR16_IN_BUFFER(nDataRead / sizeof(XnUInt16)); + nRequestSize = sizeof(XnAlgorithmParamRequest); + } + else + { + XnAlgorithmParamRequestV4* pRequest = (XnAlgorithmParamRequestV4*)pDataBuf; + pRequest->nParamID = (XnUInt8)eAlgorithmType; + pRequest->nFormat = 0; + pRequest->nResolution = (XnUInt8)nResolution; + pRequest->nFPS = 0; + pRequest->nOffset = XN_PREPARE_VAR16_IN_BUFFER(nDataRead / sizeof(XnUInt16)); + nRequestSize = sizeof(XnAlgorithmParamRequestV4); + } + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, nRequestSize, pDevicePrivateData->FWInfo.nOpcodeAlgorithmParams); + + XnUInt16 nDataSize; + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+nRequestSize, pDevicePrivateData->FWInfo.nOpcodeAlgorithmParams, + &pRelevantBuffer, nDataSize); + + + if (rc != XN_STATUS_OK) + return rc; + + XnUInt16 nReadNow = (XnUInt16)(nDataSize*sizeof(XnUInt16)); + if (nReadNow == 0) + { + break; + } + + xnOSMemCopy(pData + nDataRead, pRelevantBuffer, nReadNow); + + nDataRead += nReadNow; + } + + if (nDataRead != nAlgInfoSize) + { + XN_LOG_WARNING_RETURN(XN_STATUS_IO_DEVICE_INVALID_RESPONSE_SIZE, XN_MASK_SENSOR_PROTOCOL, "Failed getting algorithm params: expected %u bytes, but got only %u", nAlgInfoSize, nDataRead); + } + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolTakeSnapshot(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)nCMOS); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeTakeSnapshot); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeTakeSnapshot, + NULL, nDataSize); + + return rc; +} + +XnStatus XnHostProtocolGetFileList(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFirstFileId, XnFlashFile* pFileList, XnUInt16& nNumOfEntries) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUChar* pRelevantBuffer; + XnUInt32 nBytesRead = 0; + XnBool bDone = false; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting file list"); + + for (;;) + { + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nFirstFileId); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetFileList); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetFileList, + &pRelevantBuffer, nDataSize); + if (rc != XN_STATUS_OK) + { + return rc; + } + + XnUInt32 DataSizeInBytes = nDataSize*sizeof(XnUInt16); + + if (DataSizeInBytes == 0) + { + // Done + break; + } + if (nBytesRead + DataSizeInBytes > nNumOfEntries*sizeof(XnFlashFile)) + { + DataSizeInBytes = nNumOfEntries*sizeof(XnFlashFile) - nBytesRead; + bDone = true; + } + + xnOSMemCopy(((XnChar*)pFileList) + nBytesRead, pRelevantBuffer, DataSizeInBytes); + + nBytesRead += DataSizeInBytes; + nFirstFileId = XN_PREPARE_VAR16_IN_BUFFER(pFileList[nBytesRead/sizeof(XnFlashFile)-1].nId)+1; + + if (bDone) + break; + } + + nNumOfEntries = (XnUInt16)(nBytesRead/sizeof(XnFlashFile)); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolFileDownloadChunk(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileType, + XnUInt32 nOffset, XnChar* pData, XnUInt16& nChunkSize) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUChar* pRelevantBuffer; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER(nFileType); + *(XnUInt32*)((((XnUInt16*)pDataBuf)+1)) = XN_PREPARE_VAR32_IN_BUFFER(XnUInt32(nOffset/sizeof(XnUInt16))); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16)+sizeof(XnUInt32), pDevicePrivateData->FWInfo.nOpcodeDownloadFile); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16)+sizeof(XnUInt32), pDevicePrivateData->FWInfo.nOpcodeDownloadFile, + &pRelevantBuffer, nDataSize); + if (rc != XN_STATUS_OK) + { + return rc; + } + + if (nChunkSize < nDataSize*sizeof(XnUInt16)) + { + // received too much. + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + + nChunkSize = nDataSize*sizeof(XnUInt16); + + xnOSMemCopy(pData, pRelevantBuffer, nChunkSize); + + return XN_STATUS_OK; + +} + +XnStatus XnHostProtocolFileDownload(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileType, + const XnChar* strFileName) +{ + XN_FILE_HANDLE File; + XnStatus rc = XN_STATUS_OK; + + rc = xnOSOpenFile(strFileName, XN_OS_FILE_WRITE|XN_OS_FILE_TRUNCATE, &File); + XN_IS_STATUS_OK(rc); + + XnChar Buffer[MAX_PACKET_SIZE]; + XnUInt16 nChunkSize = 0; + XnUInt32 nOffset = 0; + + XnUInt32 nLastPrintBytes = 0; + + for (;;) + { + while ((nOffset - nLastPrintBytes) > 5000) + { + printf("."); + nLastPrintBytes += 5000; + } + + nChunkSize = MAX_PACKET_SIZE; + + rc = XnHostProtocolFileDownloadChunk(pDevicePrivateData, nFileType, nOffset, Buffer, nChunkSize); + + if (rc != XN_STATUS_OK || nChunkSize == 0) + { + break; + } + + rc = xnOSWriteFile(File, Buffer, nChunkSize); + if (rc != XN_STATUS_OK) + break; + nOffset += nChunkSize; + } + + printf("\n"); + + xnOSCloseFile(&File); + + return rc; +} + +#define XN_HOST_PROTOCOL_INIT_BUFFER(pBuffer) \ + XnUChar* __pBuffer = (XnUChar*)pBuffer; \ + XnUInt16 __nBufferSize = 0; + +#define XN_HOST_PROTOCOL_APPEND_PARAM(type, param) \ + *(type*)__pBuffer = (type)param; \ + __pBuffer += sizeof(type); \ + __nBufferSize += sizeof(type); + +#define XN_HOST_PROTOCOL_SIZE __nBufferSize + +XnStatus XnHostProtocolReadFlashChunk(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nOffset, XnUChar* pData, XnUInt16* nChunkSize) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUChar* pRelevantBuffer; + + XN_HOST_PROTOCOL_INIT_BUFFER(pDataBuf); + XN_HOST_PROTOCOL_APPEND_PARAM(XnUInt32, XN_PREPARE_VAR32_IN_BUFFER(nOffset)); + XN_HOST_PROTOCOL_APPEND_PARAM(XnUInt16, XN_PREPARE_VAR16_IN_BUFFER(*nChunkSize)); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, XN_HOST_PROTOCOL_SIZE, pDevicePrivateData->FWInfo.nOpcodeReadFlash); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+XN_HOST_PROTOCOL_SIZE, pDevicePrivateData->FWInfo.nOpcodeReadFlash, + &pRelevantBuffer, nDataSize); + + if (rc != XN_STATUS_OK) + { + return rc; + } + + // words to bytes + if (*nChunkSize < nDataSize) + { + // received too much. + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + + *nChunkSize = nDataSize; + + // size is in words + xnOSMemCopy(pData, pRelevantBuffer, nDataSize*sizeof(XnUInt16)); + + return XN_STATUS_OK; + +} + +XnStatus XnHostProtocolReadFlash(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nOffset, XnUInt32 nSize, XnUChar* pBuffer) +{ + XnStatus rc = XN_STATUS_OK; + + XnUInt32 nLoopOffset = nOffset; + XnUInt16 nLoopChunkSize; + XnUInt32 nReadSize = 0; + + XnUInt32 counter = 0; + while (nReadSize < nSize) + { + if (counter % 100 == 0) + { + printf("."); + } + counter++; + + // don't ask for more than MAX UINT16 + nLoopChunkSize = (XnUInt16)XN_MIN(nSize - nReadSize, 0xFFFF); + + rc = XnHostProtocolReadFlashChunk(pDevicePrivateData, nLoopOffset, pBuffer + nReadSize*sizeof(XnUInt16), &nLoopChunkSize); + + if (rc != XN_STATUS_OK) + return rc; + + if (nLoopChunkSize == 0) + return XN_STATUS_ERROR; + + nLoopOffset += nLoopChunkSize; + nReadSize += nLoopChunkSize; + } + + printf("\n"); + + return rc; +} + +XnStatus XnHostProtocolRunBIST(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nTestsMask, XnUInt32* pnFailures) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUInt32* pRelevantBuffer; + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)nTestsMask); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeBIST); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize + sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeBIST, + (XnUChar**)&pRelevantBuffer, nDataSize); + + if (rc != XN_STATUS_OK) + { + return rc; + } + + // the UINT32 received has a bit turned on for each failed module, so if all are off, everything is OK. + *pnFailures = (*pRelevantBuffer); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetCPUStats(XnDevicePrivateData* pDevicePrivateData, XnTaskCPUInfo* pTasks, XnUInt32 *pnTimesCount) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUInt32* pRelevantBuffer; + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetCPUStats); + + XnUInt16 nDataSize; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetCPUStats, + (XnUChar**)&pRelevantBuffer, nDataSize); + + if (rc != XN_STATUS_OK) + { + return rc; + } + + // check how many numbers we got + XnUInt32 nCount = nDataSize * sizeof(XnUInt16) / sizeof(XnTaskCPUInfo); + + // check if we have enough space in buffer + if (nCount > *pnTimesCount) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "CPUStats: no space in buffer for all tasks. Dropping last %d", nCount - *pnTimesCount); + nCount = *pnTimesCount; + } + + xnOSMemCopy(pTasks, pRelevantBuffer, nCount * sizeof(XnTaskCPUInfo)); + for (XnUInt32 i = 0; i < nCount; i++) + { + pTasks[i].nTimesExecuted = XN_PREPARE_VAR32_IN_BUFFER(pTasks[i].nTimesExecuted); + pTasks[i].nTimeInMicroSeconds = XN_PREPARE_VAR32_IN_BUFFER(pTasks[i].nTimeInMicroSeconds); + } + + *pnTimesCount = nCount; + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolSetAudioSampleRate(XnDevicePrivateData* pDevicePrivateData, XnSampleRate nSampleRate) +{ + EA2d_SampleRate nSample; + + switch (nSampleRate) + { + case XN_SAMPLE_RATE_8K: + nSample = A2D_SAMPLE_RATE_8KHZ; + break; + case XN_SAMPLE_RATE_11K: + nSample = A2D_SAMPLE_RATE_11KHZ; + break; + case XN_SAMPLE_RATE_12K: + nSample = A2D_SAMPLE_RATE_12KHZ; + break; + case XN_SAMPLE_RATE_16K: + nSample = A2D_SAMPLE_RATE_16KHZ; + break; + case XN_SAMPLE_RATE_22K: + nSample = A2D_SAMPLE_RATE_22KHZ; + break; + case XN_SAMPLE_RATE_24K: + nSample = A2D_SAMPLE_RATE_24KHZ; + break; + case XN_SAMPLE_RATE_32K: + nSample = A2D_SAMPLE_RATE_32KHZ; + break; + case XN_SAMPLE_RATE_44K: + nSample = A2D_SAMPLE_RATE_44KHZ; + break; + case XN_SAMPLE_RATE_48K: + nSample = A2D_SAMPLE_RATE_48KHZ; + break; + default: + return XN_STATUS_DEVICE_UNSUPPORTED_MODE; + } + + return XnHostProtocolSetParam(pDevicePrivateData, PARAM_AUDIO_SAMPLE_RATE, (XnUInt16)nSample); +} + +XnStatus XnHostProtocolGetAudioSampleRate(XnDevicePrivateData* pDevicePrivateData, XnSampleRate* pSampleRate) +{ + XnUInt16 nValue; + XnHostProtocolGetParam(pDevicePrivateData, PARAM_AUDIO_SAMPLE_RATE, nValue); + XnSampleRate nSample; + + switch (nValue) + { + case A2D_SAMPLE_RATE_8KHZ: + nSample = XN_SAMPLE_RATE_8K; + break; + case A2D_SAMPLE_RATE_11KHZ: + nSample = XN_SAMPLE_RATE_11K; + break; + case A2D_SAMPLE_RATE_12KHZ: + nSample = XN_SAMPLE_RATE_12K; + break; + case A2D_SAMPLE_RATE_16KHZ: + nSample = XN_SAMPLE_RATE_16K; + break; + case A2D_SAMPLE_RATE_22KHZ: + nSample = XN_SAMPLE_RATE_22K; + break; + case A2D_SAMPLE_RATE_24KHZ: + nSample = XN_SAMPLE_RATE_24K; + break; + case A2D_SAMPLE_RATE_32KHZ: + nSample = XN_SAMPLE_RATE_32K; + break; + case A2D_SAMPLE_RATE_44KHZ: + nSample = XN_SAMPLE_RATE_44K; + break; + case A2D_SAMPLE_RATE_48KHZ: + nSample = XN_SAMPLE_RATE_48K; + break; + default: + return XN_STATUS_DEVICE_UNSUPPORTED_MODE; + } + + *pSampleRate = nSample; + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolSetMultipleParams(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nNumOfParams, XnInnerParamData* anParams) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + XnUInt16* pCurData = (XnUInt16*)pDataBuf; + for (XnUInt16 nIndex = 0; nIndex < nNumOfParams; ++nIndex) + { + *pCurData++ = XN_PREPARE_VAR16_IN_BUFFER(anParams[nIndex].nParam); + *pCurData++ = XN_PREPARE_VAR16_IN_BUFFER(anParams[nIndex].nValue); + } + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16)*nNumOfParams*2, pDevicePrivateData->FWInfo.nOpcodeSetParam); + + XnUInt16 nDataSize; + + XnInt32 nTimesLeft = 5; + XnStatus rc = XN_STATUS_ERROR; + while (rc != XN_STATUS_OK && rc != XN_STATUS_DEVICE_PROTOCOL_BAD_PARAMS && + rc != XN_STATUS_DEVICE_PROTOCOL_INVALID_COMMAND && nTimesLeft > 0) + { + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16)*nNumOfParams*2, pDevicePrivateData->FWInfo.nOpcodeSetParam, + NULL, nDataSize); + nTimesLeft--; + } + + if (rc != XN_STATUS_OK) + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed: %s", xnGetStatusString(rc)); + + return rc; +} + +#pragma pack (push, 1) +typedef struct +{ + XnUInt16 nSetPoint; +} XnCalibrateTecRequest; +#pragma pack (pop) + +XnStatus XnHostProtocolCalibrateTec(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nSetPoint) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Calibrating TEC. Set Point: %d", nSetPoint); + + XnCalibrateTecRequest* pRequest = (XnCalibrateTecRequest*)pDataBuf; + pRequest->nSetPoint = XN_PREPARE_VAR16_IN_BUFFER(nSetPoint); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnCalibrateTecRequest), pDevicePrivateData->FWInfo.nOpcodeCalibrateTec); + + XnUInt16 nDataSize; + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnCalibrateTecRequest), pDevicePrivateData->FWInfo.nOpcodeCalibrateTec, + NULL, nDataSize); + + if (rc != XN_STATUS_OK) + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed Calibrating TEC: %s", xnGetStatusString(rc)); + else + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Calibrating TEC succeeded."); + + return rc; +} + +XnStatus XnHostProtocolGetTecData(XnDevicePrivateData* pDevicePrivateData, XnTecData* pTecData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUInt16 nDataSize; + XnStatus rc; + + if (pDevicePrivateData->FWInfo.nFWVer < XN_SENSOR_FW_VER_5_4) + { + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting TEC data..."); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetTecData); + + XnTecData* pResult; + + rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetTecData, + (XnUChar**)(&pResult), nDataSize); + + XN_IS_STATUS_OK(rc); + + pTecData->m_SetPointVoltage = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_SetPointVoltage); + pTecData->m_CompensationVoltage = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_CompensationVoltage); + pTecData->m_TecDutyCycle = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_TecDutyCycle); + pTecData->m_HeatMode = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_HeatMode); + pTecData->m_ProportionalError = XN_PREPARE_VAR32_IN_BUFFER(pResult->m_ProportionalError); + pTecData->m_IntegralError = XN_PREPARE_VAR32_IN_BUFFER(pResult->m_IntegralError); + pTecData->m_DerivativeError = XN_PREPARE_VAR32_IN_BUFFER(pResult->m_DerivativeError); + pTecData->m_ScanMode = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_ScanMode); + } + else + { + XnTecFastConvergenceData TecFastConvergenceData; + + rc = XnHostProtocolGetTecFastConvergenceData(pDevicePrivateData, &TecFastConvergenceData); + XN_IS_STATUS_OK(rc); + + pTecData->m_SetPointVoltage = 0; + pTecData->m_CompensationVoltage = 0; + pTecData->m_TecDutyCycle = TecFastConvergenceData.m_TecDutyCycle; + pTecData->m_HeatMode = TecFastConvergenceData.m_HeatMode; + pTecData->m_ProportionalError = TecFastConvergenceData.m_ProportionalError; + pTecData->m_IntegralError = TecFastConvergenceData.m_IntegralError; + pTecData->m_DerivativeError = TecFastConvergenceData.m_DerivativeError; + + // Convert the new modes (post FW 5.4) into the old modes (pre 5.4). + pTecData->m_ScanMode = (TecFastConvergenceData.m_ScanMode)-1; + } + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolGetTecFastConvergenceData (XnDevicePrivateData* pDevicePrivateData, XnTecFastConvergenceData* pTecData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUInt16 nDataSize; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting TEC Fast Convergence data..."); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetFastConvergenceTEC); + + XnTecFastConvergenceData* pResult; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetFastConvergenceTEC, + (XnUChar**)(&pResult), nDataSize); + + XN_IS_STATUS_OK(rc); + + pTecData->m_SetPointTemperature = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_SetPointTemperature); + pTecData->m_MeasuredTemperature = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_MeasuredTemperature); + pTecData->m_ProportionalError = XN_PREPARE_VAR32_IN_BUFFER(pResult->m_ProportionalError); + pTecData->m_IntegralError = XN_PREPARE_VAR32_IN_BUFFER(pResult->m_IntegralError); + pTecData->m_DerivativeError = XN_PREPARE_VAR32_IN_BUFFER(pResult->m_DerivativeError); + pTecData->m_ScanMode = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_ScanMode); + pTecData->m_HeatMode = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_HeatMode); + pTecData->m_TecDutyCycle = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_TecDutyCycle); + pTecData->m_TemperatureRange = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_TemperatureRange); + + return (XN_STATUS_OK); +} + +#pragma pack (push, 1) +typedef struct +{ + XnUInt16 nSetPoint; +} XnCalibrateEmitterRequest; +#pragma pack (pop) + +XnStatus XnHostProtocolCalibrateEmitter(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nSetPoint) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Calibrating Emitter. Set Point: %d", nSetPoint); + + XnCalibrateEmitterRequest* pRequest = (XnCalibrateEmitterRequest*)pDataBuf; + pRequest->nSetPoint = XN_PREPARE_VAR16_IN_BUFFER(nSetPoint); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnCalibrateEmitterRequest), pDevicePrivateData->FWInfo.nOpcodeCalibrateEmitter); + + XnUInt16 nDataSize; + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnCalibrateEmitterRequest), pDevicePrivateData->FWInfo.nOpcodeCalibrateEmitter, + NULL, nDataSize); + + if (rc != XN_STATUS_OK) + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed Calibrating Emitter: %s", xnGetStatusString(rc)); + else + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Calibrating Emitter succeeded."); + + return rc; +} + +XnStatus XnHostProtocolGetEmitterData(XnDevicePrivateData* pDevicePrivateData, XnEmitterData* pEmitterData) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUInt16 nDataSize; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting Emitter data..."); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetEmitterData); + + XnEmitterData* pResult; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetEmitterData, + (XnUChar**)(&pResult), nDataSize); + + XN_IS_STATUS_OK(rc); + + pEmitterData->m_State = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_State); + pEmitterData->m_SetPointVoltage = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_SetPointVoltage); + pEmitterData->m_SetPointClocks = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_SetPointClocks); + pEmitterData->m_PD_Reading = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_PD_Reading); + pEmitterData->m_EmitterSet = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_EmitterSet); + pEmitterData->m_EmitterSettingLogic = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_EmitterSettingLogic); + pEmitterData->m_LightMeasureLogic = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_LightMeasureLogic); + pEmitterData->m_IsAPCEnabled = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_IsAPCEnabled); + + // set some version specific fields + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_1) + { + pEmitterData->m_EmitterSetStepSize = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_EmitterSetStepSize); + pEmitterData->m_ApcTolerance = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_ApcTolerance); + } + else + { + pEmitterData->m_EmitterSetStepSize = 0; + pEmitterData->m_ApcTolerance = 0; + } + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_3) + { + pEmitterData->m_SubClocking = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_SubClocking); + pEmitterData->m_Precision = XN_PREPARE_VAR16_IN_BUFFER(pResult->m_Precision); + } + else + { + pEmitterData->m_SubClocking = 0; + pEmitterData->m_Precision = 0; + } + + return (XN_STATUS_OK); +} + +#pragma pack (push, 1) +typedef struct +{ + XnUInt16 nMinThreshold; + XnUInt16 nMaxThreshold; +} XnProjectorFaultRequest; +#pragma pack (pop) + +XnStatus XnHostProtocolCalibrateProjectorFault(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nMinThreshold, XnUInt16 nMaxThreshold, XnBool* pbProjectorFaultEvent) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUInt16 nDataSize; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Testing Projector Fault. Min Threshold: %u, Max Threshold: %u...", nMinThreshold, nMaxThreshold); + + XnProjectorFaultRequest* pRequest = (XnProjectorFaultRequest*)pDataBuf; + pRequest->nMinThreshold = XN_PREPARE_VAR16_IN_BUFFER(nMinThreshold); + pRequest->nMaxThreshold = XN_PREPARE_VAR16_IN_BUFFER(nMaxThreshold); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnProjectorFaultRequest), pDevicePrivateData->FWInfo.nOpcodeCalibrateProjectorFault); + + XnBool* pResult; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize + sizeof(XnProjectorFaultRequest), pDevicePrivateData->FWInfo.nOpcodeCalibrateProjectorFault, + (XnUChar**)(&pResult), nDataSize); + + XN_IS_STATUS_OK(rc); + + *pbProjectorFaultEvent = *pResult; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Projector fault event: %d", *pResult); + + return (XN_STATUS_OK); +} + +XnStatus XnDeviceSensorGetDepthAGCParams(XnUInt16 nBin, XnUInt16* pnMinParam, XnUInt16* pnMaxParam) +{ + switch (nBin) + { + case 0: + *pnMinParam = PARAM_DEPTH_AGC_BIN0_LOW; + *pnMaxParam = PARAM_DEPTH_AGC_BIN0_HIGH; + break; + case 1: + *pnMinParam = PARAM_DEPTH_AGC_BIN1_LOW; + *pnMaxParam = PARAM_DEPTH_AGC_BIN1_HIGH; + break; + case 2: + *pnMinParam = PARAM_DEPTH_AGC_BIN2_LOW; + *pnMaxParam = PARAM_DEPTH_AGC_BIN2_HIGH; + break; + case 3: + *pnMinParam = PARAM_DEPTH_AGC_BIN3_LOW; + *pnMaxParam = PARAM_DEPTH_AGC_BIN3_HIGH; + break; + default: + return XN_STATUS_DEVICE_BAD_PARAM; + } + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolSetDepthAGCBin(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nBin, XnUInt16 nMinShift, XnUInt16 nMaxShift) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt16 nMinParam; + XnUInt16 nMaxParam; + + nRetVal = XnDeviceSensorGetDepthAGCParams(nBin, &nMinParam, &nMaxParam); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolSetParam(pDevicePrivateData, nMinParam, nMinShift); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolSetParam(pDevicePrivateData, nMaxParam, nMaxShift); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolGetDepthAGCBin(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nBin, XnUInt16* pnMinShift, XnUInt16* pnMaxShift) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt16 nMinParam; + XnUInt16 nMaxParam; + + nRetVal = XnDeviceSensorGetDepthAGCParams(nBin, &nMinParam, &nMaxParam); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolGetParam(pDevicePrivateData, nMinParam, *pnMinShift); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolGetParam(pDevicePrivateData, nMaxParam, *pnMaxShift); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +#pragma pack (push, 1) + +typedef struct XnVSyncRequest +{ + XnUInt16 nUnits; + XnUInt16 nCmosID; + XnUInt16 nNumberOfFrames; +} XnVSyncRequest; + +#pragma pack (pop) + +XnStatus XnHostProtocolSetCmosBlanking(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nUnits, XnCMOSType nCMOSID, XnUInt16 nNumberOfFrames) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUInt32 nRequestSize; + + if (pDevicePrivateData->FWInfo.nFWVer >= XN_SENSOR_FW_VER_5_1) + { + XnVSyncRequest* pRequest = (XnVSyncRequest*)pDataBuf; + pRequest->nUnits = XN_PREPARE_VAR16_IN_BUFFER(nUnits); + pRequest->nCmosID = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)nCMOSID); + pRequest->nNumberOfFrames = XN_PREPARE_VAR16_IN_BUFFER(nNumberOfFrames); + nRequestSize = sizeof(XnVSyncRequest); + } + else + { + XN_LOG_WARNING_RETURN(XN_STATUS_IO_DEVICE_FUNCTION_NOT_SUPPORTED, XN_MASK_SENSOR_PROTOCOL, "Set Blanking is not supported by this firmware!"); + } + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Chaning CMOS %d Blanking to %hd (NumberOfFrames=%hu)...", nCMOSID, nUnits, nNumberOfFrames); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, nRequestSize, pDevicePrivateData->FWInfo.nOpcodeSetCmosBlanking); + + XnUInt16 nDataSize; + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize + (XnUInt16)nRequestSize, pDevicePrivateData->FWInfo.nOpcodeSetCmosBlanking, + NULL, nDataSize); + + if (rc != XN_STATUS_OK) + { + XN_LOG_WARNING_RETURN(rc, XN_MASK_SENSOR_PROTOCOL, "Failed changing CMOS %d Blanking to %hd (NumberOfFrames=%hu): %s", nCMOSID, nUnits, nNumberOfFrames, xnGetStatusString(rc)); + } + + return (XN_STATUS_OK); +} + +#pragma pack (push, 1) + +typedef struct XnGetCmosBlankingRequest +{ + XnUInt16 nCmosID; +} XnGetCmosBlankingRequest; + +typedef struct XnGetCmosBlankingReply +{ + XnUInt32 nUnits; +} XnGetCmosBlankingReply; + +#pragma pack (pop) + +XnStatus XnHostProtocolGetCmosBlanking(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOSID, XnUInt16* pnLines) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + XnGetCmosBlankingRequest* pRequest = (XnGetCmosBlankingRequest*)pDataBuf; + pRequest->nCmosID = (XnUInt16)nCMOSID; + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "Getting Cmos %d VBlanking...", nCMOSID); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnGetCmosBlankingRequest), pDevicePrivateData->FWInfo.nOpcodeGetCmosBlanking); + + XnGetCmosBlankingReply* pReply; + XnUInt16 nDataSize; + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize + sizeof(XnGetCmosBlankingRequest), pDevicePrivateData->FWInfo.nOpcodeGetCmosBlanking, + (XnUChar**)&pReply, nDataSize); + + if (rc != XN_STATUS_OK) + { + XN_LOG_WARNING_RETURN(rc, XN_MASK_SENSOR_PROTOCOL, "Failed getting Cmos %d Blanking: %s", nCMOSID, xnGetStatusString(rc)); + } + + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Cmos %d VBlanking: %u", nCMOSID, pReply->nUnits); + + *pnLines = (XnUInt16)pReply->nUnits; + + return (XN_STATUS_OK); +} + +XnStatus XnHostProtocolGetCmosPresets(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOSID, XnCmosPreset* aPresets, XnUInt32& nCount) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Reading CMOS %d supported presets...", nCMOSID); + + *(XnUInt16*)pDataBuf = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)nCMOSID); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetCmosPresets); + + XnUInt16 nDataSize; + XnCmosPreset* pValue = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize+sizeof(XnUInt16), pDevicePrivateData->FWInfo.nOpcodeGetCmosPresets, + (XnUChar**)(&pValue), nDataSize); + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed getting CMOS %d presets: %s", nCMOSID, xnGetStatusString(rc)); + return rc; + } + + XnUInt32 nReturnedCount = nDataSize * 2 / sizeof(XnCmosPreset); + if (nReturnedCount > nCount) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + XnCmosPreset* pValueEnd = pValue + nReturnedCount; + + nCount = 0; + + while (pValue < pValueEnd) + { + // workaround a FW bug - an extra preset arrives with FPS 0. Ignore it. + if (pValue->nFPS != 0) + { + aPresets[nCount].nFormat = XN_PREPARE_VAR16_IN_BUFFER(pValue->nFormat); + aPresets[nCount].nResolution = XN_PREPARE_VAR16_IN_BUFFER(pValue->nResolution); + aPresets[nCount].nFPS = XN_PREPARE_VAR16_IN_BUFFER(pValue->nFPS); + ++nCount; + } + + ++pValue; + } + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetSerialNumber (XnDevicePrivateData* pDevicePrivateData, XnChar* cpSerialNumber) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Reading sensor serial number..."); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetSerialNumber); + + XnUInt16 nDataSize; + XnUChar *serialNumberBuffer = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetSerialNumber, + (XnUChar**)(&serialNumberBuffer), nDataSize); + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed getting the sensor serial number: %s", xnGetStatusString(rc)); + return rc; + } + + serialNumberBuffer[nDataSize*2]=0; + + strcpy(cpSerialNumber, (XnChar*)serialNumberBuffer); + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetPlatformString(XnDevicePrivateData* pDevicePrivateData, XnChar* cpPlatformString) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + cpPlatformString[0] = '\0'; + + if (pDevicePrivateData->FWInfo.nOpcodeGetPlatformString == OPCODE_INVALID) + { + // for FW that doesn't support this opcode, we just return an empty string + return XN_STATUS_OK; + } + + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Reading sensor platform string..."); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetPlatformString); + + XnUInt16 nDataSize; + XnChar *platformStringBuffer = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetPlatformString, + (XnUChar**)(&platformStringBuffer), nDataSize); + if (rc != XN_STATUS_OK) + { + xnLogError(XN_MASK_SENSOR_PROTOCOL, "Failed getting the sensor platform string: %s", xnGetStatusString(rc)); + return rc; + } + + XnUInt32 nBufferUsed = 0; + for (XnUInt32 i = 0; i < (XnUInt32)nDataSize*2; ++i) + { + cpPlatformString[nBufferUsed++] = platformStringBuffer[i*2]; + } + + cpPlatformString[nBufferUsed++] = '\0'; + + return XN_STATUS_OK; +} + +XnStatus XnHostProtocolGetUsbCoreType(XnDevicePrivateData* pDevicePrivateData, XnHostProtocolUsbCore& nValue) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, 0, pDevicePrivateData->FWInfo.nOpcodeGetUsbCore); + + XnUInt16 nDataSize; + XnUInt16* pValue = NULL; + + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize, pDevicePrivateData->FWInfo.nOpcodeGetUsbCore, + (XnUChar**)(&pValue), nDataSize); + XN_IS_STATUS_OK(rc); + + nValue = (XnHostProtocolUsbCore)XN_PREPARE_VAR16_IN_BUFFER(*pValue); + + return XN_STATUS_OK; +} + +#pragma pack (push, 1) + +typedef struct XnVSetLedStateRequest +{ + XnUInt16 nLedId; + XnUInt16 nState; +} XnVSetLedStateRequest; + +#pragma pack (pop) + +XnStatus XnHostProtocolSetLedState(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nLedId, XnUInt16 nState) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUInt32 nRequestSize; + + XnVSetLedStateRequest* pRequest = (XnVSetLedStateRequest*)pDataBuf; + pRequest->nLedId = XN_PREPARE_VAR16_IN_BUFFER(nLedId); + pRequest->nState = XN_PREPARE_VAR16_IN_BUFFER(nState); + nRequestSize = sizeof(XnVSetLedStateRequest); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, nRequestSize, pDevicePrivateData->FWInfo.nOpcodeSetLedState); + + XnUInt16 nDataSize; + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize + (XnUInt16)nRequestSize, pDevicePrivateData->FWInfo.nOpcodeSetLedState, + NULL, nDataSize); + XN_IS_STATUS_OK(rc); + + return (XN_STATUS_OK); +} + +#pragma pack (push, 1) + +typedef struct XnVSetEmitterStateRequest +{ + XnUInt16 nActive; +} XnVSetEmitterStateRequest; + +#pragma pack (pop) + +XnStatus XnHostProtocolSetEmitterState(XnDevicePrivateData* pDevicePrivateData, XnBool bActive) +{ + XnUChar buffer[MAX_PACKET_SIZE] = {0}; + XnUChar* pDataBuf = buffer + pDevicePrivateData->FWInfo.nProtocolHeaderSize; + XnUInt32 nRequestSize; + + XnVSetEmitterStateRequest* pRequest = (XnVSetEmitterStateRequest*)pDataBuf; + pRequest->nActive = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)bActive); + nRequestSize = sizeof(XnVSetEmitterStateRequest); + + XnHostProtocolInitHeader(pDevicePrivateData, buffer, nRequestSize, pDevicePrivateData->FWInfo.nOpcodeEnableEmitter); + + XnUInt16 nDataSize; + XnStatus rc = XnHostProtocolExecute(pDevicePrivateData, + buffer, pDevicePrivateData->FWInfo.nProtocolHeaderSize + (XnUInt16)nRequestSize, pDevicePrivateData->FWInfo.nOpcodeEnableEmitter, + NULL, nDataSize); + XN_IS_STATUS_OK(rc); + + return (XN_STATUS_OK); +} diff --git a/Source/Drivers/PS1080/Sensor/XnHostProtocol.h b/Source/Drivers/PS1080/Sensor/XnHostProtocol.h new file mode 100644 index 0000000..3837891 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnHostProtocol.h @@ -0,0 +1,376 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef HOST_PROTOCOL_H +#define HOST_PROTOCOL_H + +#include +#include "XnParams.h" +#include "XnDeviceSensor.h" +#include "XnFirmwareTypes.h" + +#define XN_HOST_MAGIC_25 0x5053 //PS +#define XN_FW_MAGIC_25 0x5350 //SP +#define XN_HOST_MAGIC_26 0x4d47 //MG +#define XN_FW_MAGIC_26 0x4252 //BR + +#define XN_FPGA_VER_FPDB_26 0x21 +#define XN_FPGA_VER_FPDB_25 0x0 +#define XN_FPGA_VER_CDB 0x1 +#define XN_FPGA_VER_RD3 0x2 +#define XN_FPGA_VER_RD5 0x3 +#define XN_FPGA_VER_RD1081 0x4 +#define XN_FPGA_VER_RD1082 0x5 +#define XN_FPGA_VER_RD109 0x6 + +#define XN_CHIP_VER_PS1000 0x00101010 +#define XN_CHIP_VER_PS1080 0x00202020 +#define XN_CHIP_VER_PS1080A6 0x00212020 + +enum EPsProtocolOpCodes +{ + OPCODE_GET_VERSION = 0, + OPCODE_KEEP_ALIVE = 1, + OPCODE_GET_PARAM = 2, + OPCODE_SET_PARAM = 3, + OPCODE_GET_FIXED_PARAMS = 4, + OPCODE_GET_MODE = 5, + OPCODE_SET_MODE = 6, + OPCODE_GET_LOG = 7, + OPCODE_RESERVED_0 = 8, + OPCODE_RESERVED_1 = 9, + OPCODE_I2C_WRITE = 10, + OPCODE_I2C_READ = 11, + OPCODE_TAKE_SNAPSHOT = 12, + OPCODE_INIT_FILE_UPLOAD = 13, + OPCODE_WRITE_FILE_UPLOAD = 14, + OPCODE_FINISH_FILE_UPLOAD = 15, + OPCODE_DOWNLOAD_FILE = 16, + OPCODE_DELETE_FILE = 17, + OPCODE_GET_FLASH_MAP = 18, + OPCODE_GET_FILE_LIST = 19, + OPCODE_READ_AHB = 20, + OPCODE_WRITE_AHB = 21, + OPCODE_ALGORITM_PARAMS = 22, + OPCODE_SET_FILE_ATTRIBUTES = 23, + OPCODE_EXECUTE_FILE = 24, + OPCODE_READ_FLASH = 25, + OPCODE_SET_GMC_PARAMS = 26, + OPCODE_GET_CPU_STATS = 27, + OPCODE_BIST = 28, + OPCODE_CALIBRATE_TEC = 29, + OPCODE_GET_TEC_DATA = 30, + OPCODE_CALIBRATE_EMITTER = 31, + OPCODE_GET_EMITTER_DATA = 32, + OPCODE_CALIBRATE_PROJECTOR_FAULT = 33, + OPCODE_SET_CMOS_BLANKING = 34, + OPCODE_GET_CMOS_BLANKING = 35, + OPCODE_GET_CMOS_PRESETS = 36, + OPCODE_GET_SERIAL_NUMBER = 37, + OPCODE_GET_FAST_CONVERGENCE_TEC = 38, + OPCODE_GET_PLATFORM_STRING = 39, + OPCODE_GET_USB_CORE_TYPE = 40, + OPCODE_SET_LED_STATE = 41, + OPCODE_ENABLE_EMITTER = 42, + OPCODE_KILL = 999, +}; + +enum EPsProtocolOpCodes_V300 +{ + OPCODE_V300_BIST = 26, +}; + +enum XnHostProtocolOpcodes_V110 +{ + OPCODE_V110_GET_VERSION = 0, + OPCODE_V110_KEEP_ALIVE = 1, + OPCODE_V110_GET_PARAM = 2, + OPCODE_V110_SET_PARAM = 3, + OPCODE_V110_GET_FIXED_PARAMS = 4, + OPCODE_V110_GET_MODE = 5, + OPCODE_V110_SET_MODE = 6, + OPCODE_V110_GET_LOG = 7, + OPCODE_V110_GET_CMOS_REGISTER = 8, + OPCODE_V110_SET_CMOS_REGISTER = 9, + OPCODE_V110_GET_CODEC_REGISTER = 10, + OPCODE_V110_SET_CODEC_REGISTER = 11, + OPCODE_V110_TAKE_SNAPSHOT = 12, + OPCODE_V110_INIT_FILE_UPLOAD = 13, + OPCODE_V110_WRITE_FILE_UPLOAD = 14, + OPCODE_V110_FINISH_FILE_UPLOAD = 15, + OPCODE_V110_DOWNLOAD_FILE = 16, + OPCODE_V110_DELETE_FILE = 17, + OPCODE_V110_GET_FLASH_MAP = 18, + OPCODE_V110_GET_FILE_LIST = 19, + OPCODE_V110_READ_AHB = 20, + OPCODE_V110_WRITE_AHB = 21, + OPCODE_V110_ALGORITHM_PARAMS = 22, + OPCODE_V110_SET_FILE_ATTRIBUTES = 23, + OPCODE_V110_EXECUTE_FILE = 24, +}; + +enum EPsProtocolOpCodes_V017 +{ + OPCODE_V017_GET_VERSION = 0, + OPCODE_V017_KEEP_ALIVE = 1, + OPCODE_V017_GET_PARAM = 2, + OPCODE_V017_SET_PARAM = 3, + OPCODE_V017_GET_FIXED_PARAMS = 4, + OPCODE_V017_RESET = 5, + OPCODE_V017_GET_LOG = 6, + OPCODE_V017_GET_CMOS_REGISTER = 7, + OPCODE_V017_SET_CMOS_REGISTER = 8, + OPCODE_V017_GET_CODEC_REGISTER = 9, + OPCODE_V017_SET_CODEC_REGISTER = 10, + OPCODE_V017_TAKE_SNAPSHOT = 11, + OPCODE_V017_INIT_FILE_UPLOAD = 12, + OPCODE_V017_WRITE_FILE_UPLOAD = 13, + OPCODE_V017_FINISH_FILE_UPLOAD = 14, + OPCODE_V017_DOWNLOAD_FILE = 15, + OPCODE_V017_DELETE_FILE = 16, + OPCODE_V017_GET_FLASH_MAP = 17, + OPCODE_V017_GET_FILE_LIST = 18, + OPCODE_V017_READ_AHB = 19, + OPCODE_V017_WRITE_AHB = 20, + OPCODE_V017_ALGORITM_PARAMS = 21, +}; + +#define OPCODE_INVALID 0xffff + +typedef enum +{ + XN_HOST_PROTOCOL_ALGORITHM_DEPTH_INFO = 0x00, + XN_HOST_PROTOCOL_ALGORITHM_REGISTRATION = 0x02, + XN_HOST_PROTOCOL_ALGORITHM_PADDING = 0x03, + XN_HOST_PROTOCOL_ALGORITHM_BLANKING = 0x06, + XN_HOST_PROTOCOL_ALGORITHM_DEVICE_INFO = 0x07, + XN_HOST_PROTOCOL_ALGORITHM_FREQUENCY = 0x80 +} XnHostProtocolAlgorithmType; + +typedef enum +{ + XN_HOST_PROTOCOL_MODE_WEBCAM = 0, + XN_HOST_PROTOCOL_MODE_PS, + XN_HOST_PROTOCOL_MODE_MAINTENANCE, + XN_HOST_PROTOCOL_MODE_SOFT_RESET, + XN_HOST_PROTOCOL_MODE_REBOOT, + XN_HOST_PROTOCOL_MODE_SUSPEND, + XN_HOST_PROTOCOL_MODE_RESUME, + XN_HOST_PROTOCOL_MODE_INIT, + XN_HOST_PROTOCOL_MODE_SYSTEM_RESTORE, + XN_HOST_PROTOCOL_MODE_WAIT_FOR_ENUM, + XN_HOST_PROTOCOL_MODE_SAFE_MODE +} XnHostProtocolModeType; + +enum XnHostProtocolNacks +{ + ACK = 0, + NACK_UNKNOWN_ERROR = 1, + NACK_INVALID_COMMAND = 2, + NACK_BAD_PACKET_CRC = 3, + NACK_BAD_PACKET_SIZE = 4, + NACK_BAD_PARAMS = 5, + NACK_I2C_TRANSACTION_FAILED = 6, + NACK_FILE_NOT_FOUND = 7, + NACK_FILE_CREATE_FAILURE = 8, + NACK_FILE_WRITE_FAILURE = 9, + NACK_FILE_DELETE_FAILURE = 10, + NACK_FILE_READ_FAILURE = 11, + NACK_BAD_COMMAND_SIZE = 12, + NACK_NOT_READY = 13, + NACK_OVERFLOW = 14, + NACK_OVERLAY_NOT_LOADED = 15, + NACK_FILE_SYSTEM_LOCKED = 16, +}; + +typedef enum +{ + A2D_SAMPLE_RATE_48KHZ, + A2D_SAMPLE_RATE_44KHZ, + A2D_SAMPLE_RATE_32KHZ, + A2D_SAMPLE_RATE_24KHZ, + A2D_SAMPLE_RATE_22KHZ, + A2D_SAMPLE_RATE_16KHZ, + A2D_SAMPLE_RATE_12KHZ, + A2D_SAMPLE_RATE_11KHZ, + A2D_SAMPLE_RATE_8KHZ, + A2D_NUM_OF_SAMPLE_RATES +} EA2d_SampleRate; + +typedef enum XnHostProtocolUsbCore +{ + XN_USB_CORE_JANGO = 0, + XN_USB_CORE_GADGETFS = 1, +} XnHostProtocolUsbCore; + +#pragma pack(push,1) +typedef struct +{ + XnUInt16 nMagic; + XnUInt16 nSize; + XnUInt16 nOpcode; + XnUInt16 nId; + XnUInt16 nCRC16; +} XnHostProtocolHeaderV25; + +typedef struct +{ + XnUInt16 nMagic; + XnUInt16 nSize; + XnUInt16 nOpcode; + XnUInt16 nId; +} XnHostProtocolHeaderV26; + +typedef struct +{ + XnUInt16 nErrorCode; +} XnHostProtocolReplyHeader; + +typedef XnDeviceSensorGMCPoint XnHostProtocolGMCPoint_1080; + +typedef struct XnHostProtocolGMCPoint_1000 +{ + XnDeviceSensorGMCPoint m_GMCPoint; + XnUInt16 m_Dummy; +} XnHostProtocolGMCPoint_1000; + +typedef struct XnHostProtocolGMCLastConfData +{ + XnInt16 nLast; + XnUInt16 nRICCLast; + XnFloat fRICC_IIR; +} XnHostProtocolGMCLastConfData; + +typedef enum XnHostProtocolGMCMode +{ + GMC_NORMAL_MODE = 0, + GMC_SCAN_MODE +} XnHostProtocolGMCMode; + +typedef struct XnHostProtocolGMCLastPacketData +{ + XnUInt16 m_GMCMode; + XnUInt16 m_CoveragePass; + XnHostProtocolGMCLastConfData m_LastConfData; + XnFloat m_A; + XnFloat m_B; + XnFloat m_C; + XnInt16 m_N; + XnUInt16 m_RICC; + XnUInt32 m_StartB; + XnUInt32 m_DeltaB; + XnInt16 m_FlashStoredRefOffset; +} XnHostProtocolGMCLastPacketData; + +typedef struct XnBestTecConf +{ + XnUInt16 nBestHopsCount; // Lowest hops count among all unstable points + XnUInt32 nBestSetPoint; // The TEC set point that gave m_BestHopsCount + XnInt32 nBestStep; // The TEC scan step that gave m_BestHopsCount +} XnBestTecConf; + +typedef struct XnWavelengthCorrectionDebugPacket +{ + XnFloat fBLast; + XnFloat fBCurrent; + XnUInt16 nIsHop; + XnUInt32 nCurrentSlidingWindow; + XnUInt16 nCurrentHopsCount; + XnUInt16 nIsTecCalibrated; + XnUInt32 nWaitPeriod; + XnUInt16 nIsWavelengthUnstable; + XnBestTecConf BestConf; + XnUInt16 nIsTotallyUnstable; //whole scan no stable point + XnUInt32 nConfiguredTecSetPoint; // 0 if not configured + XnInt32 nCurrentStep; +} XnWavelengthCorrectionDebugPacket; + +#pragma pack(pop) + +////////////////////////////////////// Exported h file should be only from here down +// Exported params + +// All implemented protocol commands +// Init +XnStatus XnHostProtocolInitFWParams(XnDevicePrivateData* pDevicePrivateData, XnUInt8 nMajor, XnUInt8 nMinor, XnUInt16 nBuild, XnHostProtocolUsbCore usb, XnBool bGuessed); + +XnStatus XnHostProtocolKeepAlive (XnDevicePrivateData* pDevicePrivateData); +XnStatus XnHostProtocolGetVersion (const XnDevicePrivateData* pDevicePrivateData, XnVersions& Version); +XnStatus XnHostProtocolAlgorithmParams (XnDevicePrivateData* pDevicePrivateData, + XnHostProtocolAlgorithmType eAlgorithmType, + void* pAlgorithmInformation, XnUInt16 nAlgInfoSize, XnResolutions nResolution, XnUInt16 nFPS); +XnStatus XnHostProtocolSetImageResolution(XnDevicePrivateData* pDevicePrivateData, XnUInt32 nResolutionParamName, XnResolutions nRes); +XnStatus XnHostProtocolSetDepthResolution(XnDevicePrivateData* pDevicePrivateData, XnResolutions nRes); +XnStatus XnHostProtocolGetFixedParams(XnDevicePrivateData* pDevicePrivateData, XnFixedParams& FixedParams); + +XnStatus XnHostProtocolSetAudioSampleRate(XnDevicePrivateData* pDevicePrivateData, XnSampleRate nSampleRate); +XnStatus XnHostProtocolGetAudioSampleRate(XnDevicePrivateData* pDevicePrivateData, XnSampleRate* pSampleRate); + +XnStatus XnHostProtocolSetMode (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nMode); +XnStatus XnHostProtocolGetMode (XnDevicePrivateData* pDevicePrivateData, XnUInt16& nMode); + +XnStatus XnHostProtocolSetParam (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nParam, XnUInt16 nValue); +XnStatus XnHostProtocolSetMultipleParams(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nNumOfParams, XnInnerParamData* anParams); +XnStatus XnHostProtocolReset(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nResetType); + +XnStatus XnHostProtocolGetParam (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nParam, XnUInt16& nValue); + +XnStatus XnHostProtocolSetDepthAGCBin(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nBin, XnUInt16 nMinShift, XnUInt16 nMaxShift); +XnStatus XnHostProtocolGetDepthAGCBin(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nBin, XnUInt16* pnMinShift, XnUInt16* pnMaxShift); + +XnStatus XnHostProtocolSetCmosBlanking (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nLines, XnCMOSType nCMOSID, XnUInt16 nNumberOfFrames); +XnStatus XnHostProtocolGetCmosBlanking (XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOSID, XnUInt16* pnLines); + +XnStatus XnHostProtocolGetCmosPresets (XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOSID, XnCmosPreset* aPresets, XnUInt32& nCount); + +XnStatus XnHostProtocolGetSerialNumber (XnDevicePrivateData* pDevicePrivateData, XnChar* cpSerialNumber); +XnStatus XnHostProtocolGetPlatformString(XnDevicePrivateData* pDevicePrivateData, XnChar* cpPlatformString); + +XnStatus XnHostProtocolGetCMOSRegister(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, XnUInt16& nValue); +XnStatus XnHostProtocolSetCMOSRegister (XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, XnUInt16 nValue); +XnStatus XnHostProtocolGetCMOSRegisterI2C(XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, XnUInt16& nValue); +XnStatus XnHostProtocolSetCMOSRegisterI2C (XnDevicePrivateData* pDevicePrivateData, XnCMOSType nCMOS, XnUInt16 nAddress, XnUInt16 nValue); +XnStatus XnHostProtocolReadAHB (XnDevicePrivateData* pDevicePrivateData, XnUInt32 nAddress, XnUInt32 &nValue); +XnStatus XnHostProtocolWriteAHB (XnDevicePrivateData* pDevicePrivateData, XnUInt32 nAddress, XnUInt32 nValue, XnUInt32 nMask); +XnStatus XnHostProtocolGetUsbCoreType (XnDevicePrivateData* pDevicePrivateData, XnHostProtocolUsbCore& nValue); +XnStatus XnHostProtocolSetLedState (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nLedId, XnUInt16 nState); +XnStatus XnHostProtocolSetEmitterState (XnDevicePrivateData* pDevicePrivateData, XnBool bActive); +XnStatus XnHostProtocolUpdateSupportedImageModes(XnDevicePrivateData* pDevicePrivateData); + +// Commands.txt +XnStatus XnHostProtocolGetLog (XnDevicePrivateData* pDevicePrivateData, XnChar* csBuffer, XnUInt32 nBufferSize); +XnStatus XnHostProtocolFileUpload (XnDevicePrivateData* pDevicePrivateData, XnUInt32 nOffset, const XnChar* strFileName, XnUInt16 nAttributes); +XnStatus XnHostProtocolFileDownload (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileType, const XnChar* strFileName); +XnStatus XnHostProtocolReadFlash (XnDevicePrivateData* pDevicePrivateData, XnUInt32 nOffset, XnUInt32 nSize, XnUChar* pBuffer); +XnStatus XnHostProtocolRunBIST (XnDevicePrivateData* pDevicePrivateData, XnUInt32 nTestsMask, XnUInt32* pnFailures); +XnStatus XnHostProtocolGetCPUStats (XnDevicePrivateData* pDevicePrivateData, XnTaskCPUInfo* pTasks, XnUInt32 *pnTimesCount); +XnStatus XnHostProtocolCalibrateTec (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nSetPoint); +XnStatus XnHostProtocolGetTecData (XnDevicePrivateData* pDevicePrivateData, XnTecData* pTecData); +XnStatus XnHostProtocolGetTecFastConvergenceData (XnDevicePrivateData* pDevicePrivateData, XnTecFastConvergenceData* pTecData); +XnStatus XnHostProtocolCalibrateEmitter (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nSetPoint); +XnStatus XnHostProtocolGetEmitterData (XnDevicePrivateData* pDevicePrivateData, XnEmitterData* pEmitterData); +XnStatus XnHostProtocolCalibrateProjectorFault (XnDevicePrivateData* pDevicePrivateData, XnUInt16 nMinThreshold, XnUInt16 nMaxThreshold, XnBool* pbProjectorFaultEvent); +XnStatus XnHostProtocolGetFileList(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFirstFileId, XnFlashFile* pFileList, XnUInt16& nNumOfEntries); +XnStatus XnHostProtocolDeleteFile(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileId); +XnStatus XnHostProtocolWriteI2C(XnDevicePrivateData* pDevicePrivateData, const XnI2CWriteData* pI2CWriteData); +XnStatus XnHostProtocolReadI2C(XnDevicePrivateData* pDevicePrivateData, XnI2CReadData* pI2CReadData); +XnStatus XnHostProtocolSetFileAttributes(XnDevicePrivateData* pDevicePrivateData, XnUInt16 nFileId, XnUInt16 nAttributes); + +#endif diff --git a/Source/Drivers/PS1080/Sensor/XnIRProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnIRProcessor.cpp new file mode 100644 index 0000000..ddf6870 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnIRProcessor.cpp @@ -0,0 +1,347 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnIRProcessor.h" +#include +#include "XnSensor.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- + +/* The size of an input element for unpacking. */ +#define XN_INPUT_ELEMENT_SIZE 5 +/* The size of an output element for unpacking. */ +#define XN_OUTPUT_ELEMENT_SIZE 8 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnIRProcessor::XnIRProcessor(XnSensorIRStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnFrameStreamProcessor(pStream, pHelper, pBufferManager, XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_START, XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_END), + m_nRefTimestamp(0), + m_DepthCMOSType(pHelper->GetFixedParams()->GetDepthCmosType()) +{ +} + +XnIRProcessor::~XnIRProcessor() +{ +} + +XnStatus XnIRProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnFrameStreamProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_ContinuousBuffer, XN_INPUT_ELEMENT_SIZE); + + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_GRAY16: + break; + case ONI_PIXEL_FORMAT_RGB888: + XN_VALIDATE_BUFFER_ALLOCATE(m_UnpackedBuffer, GetExpectedOutputSize()); + break; + default: + assert(0); + return XN_STATUS_ERROR; + } + + return (XN_STATUS_OK); +} + +XnStatus XnIRProcessor::Unpack10to16(const XnUInt8* pcInput, const XnUInt32 nInputSize, XnUInt16* pnOutput, XnUInt32* pnActualRead, XnUInt32* pnOutputSize) +{ + XnInt32 cInput = 0; + const XnUInt8* pOrigInput = pcInput; + + XnUInt32 nElements = nInputSize / XN_INPUT_ELEMENT_SIZE; // floored + XnUInt32 nNeededOutput = nElements * XN_OUTPUT_ELEMENT_SIZE; + + *pnActualRead = 0; + + if (*pnOutputSize < nNeededOutput) + { + *pnOutputSize = 0; + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + // Convert the 10bit packed data into 16bit shorts + + for (XnUInt32 nElem = 0; nElem < nElements; ++nElem) + { + //1a + cInput = *pcInput; + *pnOutput = (cInput & 0xFF) << 2; + + //1b + pcInput++; + cInput = *pcInput; + *pnOutput = *pnOutput | ((cInput & 0xC0) >> 6); + pnOutput++; + + //2a + *pnOutput = (cInput & 0x3F) << 4; + + //2b + pcInput++; + cInput = *pcInput; + *pnOutput = *pnOutput | ((cInput & 0xF0) >> 4); + pnOutput++; + + //3a + *pnOutput = (cInput & 0x0F) << 6; + + //3b + pcInput++; + cInput = *pcInput; + *pnOutput = *pnOutput | ((cInput & 0xFC) >> 2); + pnOutput++; + + //4a + *pnOutput = (cInput & 0x3) << 8; + + //4b + pcInput++; + cInput = *pcInput; + *pnOutput = *pnOutput | (cInput & 0xFF); + pnOutput++; + + pcInput++; + } + + *pnActualRead = (XnUInt32)(pcInput - pOrigInput); + *pnOutputSize = nNeededOutput; + return XN_STATUS_OK; +} + +void XnIRProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnIRProcessor::ProcessFramePacketChunk") + + // if output format is Gray16, we can write directly to output buffer. otherwise, we need + // to write to a temp buffer. + XnBuffer* pWriteBuffer = (GetStream()->GetOutputFormat() == ONI_PIXEL_FORMAT_GRAY16) ? GetWriteBuffer() : &m_UnpackedBuffer; + + if (m_ContinuousBuffer.GetSize() != 0) + { + // fill in to a whole element + XnUInt32 nReadBytes = XN_MIN(nDataSize, XN_INPUT_ELEMENT_SIZE - m_ContinuousBuffer.GetSize()); + m_ContinuousBuffer.UnsafeWrite(pData, nReadBytes); + pData += nReadBytes; + nDataSize -= nReadBytes; + + if (m_ContinuousBuffer.GetSize() == XN_INPUT_ELEMENT_SIZE) + { + // process it + XnUInt32 nActualRead = 0; + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + if (XN_STATUS_OK != Unpack10to16(m_ContinuousBuffer.GetData(), XN_INPUT_ELEMENT_SIZE, (XnUInt16*)pWriteBuffer->GetUnsafeWritePointer(), &nActualRead, &nOutputSize)) + WriteBufferOverflowed(); + else + pWriteBuffer->UnsafeUpdateSize(nOutputSize); + + m_ContinuousBuffer.Reset(); + } + } + + XnUInt32 nActualRead = 0; + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + if (XN_STATUS_OK != Unpack10to16(pData, nDataSize, (XnUInt16*)pWriteBuffer->GetUnsafeWritePointer(), &nActualRead, &nOutputSize)) + { + WriteBufferOverflowed(); + } + else + { + pWriteBuffer->UnsafeUpdateSize(nOutputSize); + + pData += nActualRead; + nDataSize -= nActualRead; + + // if we have any bytes left, store them for next packet + if (nDataSize > 0) + { + // no need to check for overflow. there can not be a case in which more than XN_INPUT_ELEMENT_SIZE + // are left. + m_ContinuousBuffer.UnsafeWrite(pData, nDataSize); + } + } + + XN_PROFILING_END_SECTION +} + +void IRto888(XnUInt16* pInput, XnUInt32 nInputSize, XnUInt8* pOutput, XnUInt32* pnOutputSize) +{ + XnUInt16* pInputEnd = pInput + nInputSize; + XnUInt8* pOutputOrig = pOutput; + XnUInt8* pOutputEnd = pOutput + *pnOutputSize; + + while (pInput != pInputEnd && pOutput < pOutputEnd) + { + *pOutput = (XnUInt8)((*pInput)>>2); + *(pOutput+1) = *pOutput; + *(pOutput+2) = *pOutput; + + pOutput+=3; + pInput++; + } + + *pnOutputSize = (XnUInt32)(pOutput - pOutputOrig); +} + +void XnIRProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XN_PROFILING_START_SECTION("XnIRProcessor::OnEndOfFrame") + + // if there are bytes left in continuous buffer, then we have a corrupt frame + if (m_ContinuousBuffer.GetSize() != 0) + { + xnLogWarning(XN_MASK_SENSOR_READ, "IR buffer is corrupt. There are left over bytes (invalid size)"); + FrameIsCorrupted(); + } + + // if data was written to temp buffer, convert it now + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_GRAY16: + break; + case ONI_PIXEL_FORMAT_RGB888: + { + XnUInt32 nOutputSize = GetWriteBuffer()->GetFreeSpaceInBuffer(); + IRto888((XnUInt16*)m_UnpackedBuffer.GetData(), m_UnpackedBuffer.GetSize() / sizeof(XnUInt16), GetWriteBuffer()->GetUnsafeWritePointer(), &nOutputSize); + GetWriteBuffer()->UnsafeUpdateSize(nOutputSize); + m_UnpackedBuffer.Reset(); + } + break; + default: + assert(0); + return; + } + + // calculate expected size + XnUInt32 width = GetStream()->GetXRes(); + XnUInt32 height = GetStream()->GetYRes(); + XnUInt32 actualHeight = height; + + // when cropping is turned on, actual depth size is smaller + if (GetStream()->m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + width = (XnUInt32)GetStream()->m_FirmwareCropSizeX.GetValue(); + height = (XnUInt32)GetStream()->m_FirmwareCropSizeY.GetValue(); + actualHeight = height; + } + else if (GetStream()->GetResolution() != XN_RESOLUTION_SXGA) + { + if (m_DepthCMOSType == XN_DEPTH_CMOS_MT9M001) + { + // there are additional 8 rows (this is how the CMOS is configured) + actualHeight += 8; + } + } + else + { + if (m_DepthCMOSType == XN_DEPTH_CMOS_AR130) + { + // there missing 64 rows (this is how the CMOS is configured) + actualHeight -= 64; + } + } + + XnUInt32 nExpectedBufferSize = width * actualHeight * GetStream()->GetBytesPerPixel(); + + if (GetWriteBuffer()->GetSize() != nExpectedBufferSize) + { + xnLogWarning(XN_MASK_SENSOR_READ, "IR buffer is corrupt. Size is %u (!= %u)", GetWriteBuffer()->GetSize(), nExpectedBufferSize); + FrameIsCorrupted(); + } + + // don't report additional rows out (so we're not using the expected buffer size) + GetWriteBuffer()->UnsafeSetSize(width * height * GetStream()->GetBytesPerPixel()); + + OniFrame* pFrame = GetWriteFrame(); + pFrame->sensorType = ONI_SENSOR_IR; + + pFrame->videoMode.pixelFormat = GetStream()->GetOutputFormat(); + pFrame->videoMode.resolutionX = GetStream()->GetXRes(); + pFrame->videoMode.resolutionY = GetStream()->GetYRes(); + pFrame->videoMode.fps = GetStream()->GetFPS(); + pFrame->width = (int)width; + pFrame->height = (int)height; + + if (GetStream()->m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + pFrame->cropOriginX = (int)GetStream()->m_FirmwareCropOffsetX.GetValue(); + pFrame->cropOriginY = (int)GetStream()->m_FirmwareCropOffsetY.GetValue(); + pFrame->croppingEnabled = TRUE; + } + else + { + pFrame->cropOriginX = 0; + pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + } + + pFrame->stride = pFrame->width * GetStream()->GetBytesPerPixel(); + + XnFrameStreamProcessor::OnEndOfFrame(pHeader); + m_ContinuousBuffer.Reset(); + + XN_PROFILING_END_SECTION +} + +XnUInt64 XnIRProcessor::CreateTimestampFromDevice(XnUInt32 nDeviceTimeStamp) +{ + XnUInt64 nNow; + xnOSGetHighResTimeStamp(&nNow); + + // There's a firmware bug, causing IR timestamps not to advance if depth stream is off. + // If so, we need to create our own timestamps. + if (m_pDevicePrivateData->pSensor->GetFirmware()->GetParams()->m_Stream1Mode.GetValue() != XN_VIDEO_STREAM_DEPTH) + { + if (m_nRefTimestamp == 0) + { + m_nRefTimestamp = nNow; + } + + return nNow - m_nRefTimestamp; + } + else + { + XnUInt64 nResult = XnFrameStreamProcessor::CreateTimestampFromDevice(nDeviceTimeStamp); + + // keep it as ref so that if depth is turned off, we'll continue from there + m_nRefTimestamp = nNow - nResult; + + return nResult; + } +} + +void XnIRProcessor::OnFrameReady(XnUInt32 nFrameID, XnUInt64 nFrameTS) +{ + XnFrameStreamProcessor::OnFrameReady(nFrameID, nFrameTS); + + m_pDevicePrivateData->pSensor->GetFPSCalculator()->MarkIr(nFrameID, nFrameTS); +} diff --git a/Source/Drivers/PS1080/Sensor/XnIRProcessor.h b/Source/Drivers/PS1080/Sensor/XnIRProcessor.h new file mode 100644 index 0000000..7b646be --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnIRProcessor.h @@ -0,0 +1,72 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_IR_PROCESSOR_H__ +#define __XN_IR_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFrameStreamProcessor.h" +#include "XnSensorIRStream.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnIRProcessor : public XnFrameStreamProcessor +{ +public: + XnIRProcessor(XnSensorIRStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + virtual ~XnIRProcessor(); + + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual XnUInt64 CreateTimestampFromDevice(XnUInt32 nDeviceTimeStamp); + virtual void OnFrameReady(XnUInt32 nFrameID, XnUInt64 nFrameTS); + + //--------------------------------------------------------------------------- + // Internal Functions + //--------------------------------------------------------------------------- +private: + XnStatus Unpack10to16(const XnUInt8* pcInput, const XnUInt32 nInputSize, XnUInt16* pnOutput, XnUInt32* pnActualRead, XnUInt32* pnOutputSize); + inline XnSensorIRStream* GetStream() + { + return (XnSensorIRStream*)XnFrameStreamProcessor::GetStream(); + } + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + /* A buffer to store bytes till we have enough to unpack. */ + XnBuffer m_ContinuousBuffer; + XnBuffer m_UnpackedBuffer; + XnUInt64 m_nRefTimestamp; // needed for firmware bug workaround + XnDepthCMOSType m_DepthCMOSType; +}; + +#endif //__XN_IR_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnImageProcessor.cpp new file mode 100644 index 0000000..fb7a656 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnImageProcessor.cpp @@ -0,0 +1,161 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" +#include "XnSensor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnImageProcessor::XnImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager, XnBool bCompressedOutput /* = FALSE */) : + XnFrameStreamProcessor(pStream, pHelper, pBufferManager, XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_START, XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_END), + m_bCompressedOutput(bCompressedOutput) +{ +} + +XnImageProcessor::~XnImageProcessor() +{ + // unregister from properties (otherwise, callbacks will be called with deleted pointer...) + GetStream()->XResProperty().OnChangeEvent().Unregister(m_hXResCallback); + GetStream()->YResProperty().OnChangeEvent().Unregister(m_hYResCallback); + GetStream()->m_FirmwareCropSizeX.OnChangeEvent().Unregister(m_hXCropCallback); + GetStream()->m_FirmwareCropSizeY.OnChangeEvent().Unregister(m_hYCropCallback); + GetStream()->m_FirmwareCropMode.OnChangeEvent().Unregister(m_hCropEnabledCallback); +} + +XnStatus XnImageProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnFrameStreamProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetStream()->XResProperty().OnChangeEvent().Register(ActualResChangedCallback, this, m_hXResCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetStream()->YResProperty().OnChangeEvent().Register(ActualResChangedCallback, this, m_hYResCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetStream()->m_FirmwareCropSizeX.OnChangeEvent().Register(ActualResChangedCallback, this, m_hXCropCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetStream()->m_FirmwareCropSizeY.OnChangeEvent().Register(ActualResChangedCallback, this, m_hYCropCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetStream()->m_FirmwareCropMode.OnChangeEvent().Register(ActualResChangedCallback, this, m_hCropEnabledCallback); + XN_IS_STATUS_OK(nRetVal); + + CalcActualRes(); + + return (XN_STATUS_OK); +} + +XnUInt32 XnImageProcessor::CalculateExpectedSize() +{ + XnUInt32 nExpectedDepthBufferSize = GetStream()->GetXRes() * GetStream()->GetYRes(); + + // when cropping is turned on, actual depth size is smaller + if (GetStream()->m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nExpectedDepthBufferSize = (XnUInt32)(GetStream()->m_FirmwareCropSizeX.GetValue() * GetStream()->m_FirmwareCropSizeY.GetValue()); + } + + nExpectedDepthBufferSize *= GetStream()->GetBytesPerPixel(); + + return nExpectedDepthBufferSize; +} + +void XnImageProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + if (!m_bCompressedOutput) + { + // make sure data size is right + XnUInt32 nExpectedSize = CalculateExpectedSize(); + if (GetWriteBuffer()->GetSize() != nExpectedSize) + { + xnLogWarning(XN_MASK_SENSOR_READ, "Read: Image buffer is corrupt. Size is %u (!= %u)", GetWriteBuffer()->GetSize(), nExpectedSize); + FrameIsCorrupted(); + } + } + + OniFrame* pFrame = GetWriteFrame(); + pFrame->sensorType = ONI_SENSOR_COLOR; + + pFrame->videoMode.pixelFormat = GetStream()->GetOutputFormat(); + pFrame->videoMode.resolutionX = GetStream()->GetXRes(); + pFrame->videoMode.resolutionY = GetStream()->GetYRes(); + pFrame->videoMode.fps = GetStream()->GetFPS(); + + if (GetStream()->m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + pFrame->width = (int)GetStream()->m_FirmwareCropSizeX.GetValue(); + pFrame->height = (int)GetStream()->m_FirmwareCropSizeY.GetValue(); + pFrame->cropOriginX = (int)GetStream()->m_FirmwareCropOffsetX.GetValue(); + pFrame->cropOriginY = (int)GetStream()->m_FirmwareCropOffsetY.GetValue(); + pFrame->croppingEnabled = TRUE; + } + else + { + pFrame->width = pFrame->videoMode.resolutionX; + pFrame->height = pFrame->videoMode.resolutionY; + pFrame->cropOriginX = 0; + pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + } + + pFrame->stride = pFrame->width * GetStream()->GetBytesPerPixel(); + + // call base + XnFrameStreamProcessor::OnEndOfFrame(pHeader); +} + +void XnImageProcessor::OnFrameReady(XnUInt32 nFrameID, XnUInt64 nFrameTS) +{ + XnFrameStreamProcessor::OnFrameReady(nFrameID, nFrameTS); + + m_pDevicePrivateData->pSensor->GetFPSCalculator()->MarkColor(nFrameID, nFrameTS); +} + +void XnImageProcessor::CalcActualRes() +{ + if (GetStream()->m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + m_nActualXRes = (XnUInt32)GetStream()->m_FirmwareCropSizeX.GetValue(); + m_nActualYRes = (XnUInt32)GetStream()->m_FirmwareCropSizeY.GetValue(); + } + else + { + m_nActualXRes = GetStream()->GetXRes(); + m_nActualYRes = GetStream()->GetYRes(); + } +} + +XnStatus XnImageProcessor::ActualResChangedCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnImageProcessor* pThis = (XnImageProcessor*)pCookie; + pThis->CalcActualRes(); + return XN_STATUS_OK; +} + diff --git a/Source/Drivers/PS1080/Sensor/XnImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnImageProcessor.h new file mode 100644 index 0000000..0f75163 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnImageProcessor.h @@ -0,0 +1,76 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_IMAGE_PROCESSOR_H__ +#define __XN_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFrameStreamProcessor.h" +#include "XnSensorImageStream.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +class XnImageProcessor : public XnFrameStreamProcessor +{ +public: + XnImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager, XnBool bCompressedOutput = FALSE); + virtual ~XnImageProcessor(); + + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnFrameReady(XnUInt32 nFrameID, XnUInt64 nFrameTS); + + //--------------------------------------------------------------------------- + // Helper Functions + //--------------------------------------------------------------------------- + inline XnSensorImageStream* GetStream() + { + return (XnSensorImageStream*)XnFrameStreamProcessor::GetStream(); + } + + XnUInt32 GetActualXRes() { return m_nActualXRes; } + XnUInt32 GetActualYRes() { return m_nActualYRes; } + +private: + XnUInt32 CalculateExpectedSize(); + void CalcActualRes(); + static XnStatus XN_CALLBACK_TYPE ActualResChangedCallback(const XnProperty* pSender, void* pCookie); + + XnUInt32 m_nActualXRes; + XnUInt32 m_nActualYRes; + + XnCallbackHandle m_hXResCallback; + XnCallbackHandle m_hYResCallback; + XnCallbackHandle m_hXCropCallback; + XnCallbackHandle m_hYCropCallback; + XnCallbackHandle m_hCropEnabledCallback; + + XnBool m_bCompressedOutput; +}; + +#endif //__XN_IMAGE_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.cpp new file mode 100644 index 0000000..f8353d0 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.cpp @@ -0,0 +1,56 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnJpegImageProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnJpegImageProcessor::XnJpegImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager, TRUE) +{ + SetAllowDoubleSOFPackets(TRUE); +} + +XnJpegImageProcessor::~XnJpegImageProcessor() +{ +} + +void XnJpegImageProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnJpegImageProcessor::ProcessFramePacketChunk"); + + // when image is uncompressed, we can just copy it directly to write buffer + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + // make sure we have enough room + if (CheckWriteBufferForOverflow(nDataSize)) + { + pWriteBuffer->UnsafeWrite(pData, nDataSize); + } + + XN_PROFILING_END_SECTION; +} + diff --git a/Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.h new file mode 100644 index 0000000..c47583d --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnJpegImageProcessor.h @@ -0,0 +1,44 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_JPEG_IMAGE_PROCESSOR_H__ +#define __XN_JPEG_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnJpegImageProcessor : public XnImageProcessor +{ +public: + XnJpegImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnJpegImageProcessor(); + +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); +}; + +#endif // __XN_JPEG_IMAGE_PROCESSOR_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.cpp new file mode 100644 index 0000000..3227ccd --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.cpp @@ -0,0 +1,109 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnJpegToRGBImageProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnJpegToRGBImageProcessor::XnJpegToRGBImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager) +{ + SetAllowDoubleSOFPackets(TRUE); +} + +XnJpegToRGBImageProcessor::~XnJpegToRGBImageProcessor() +{ + XnStreamFreeUncompressImageJ(&m_JPEGContext); +} + +XnStatus XnJpegToRGBImageProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnImageProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_RawData, GetExpectedOutputSize()); + + nRetVal = XnStreamInitUncompressImageJ(&m_JPEGContext); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnJpegToRGBImageProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnJpegToRGBImageProcessor::ProcessFramePacketChunk") + + // append to raw buffer + if (m_RawData.GetFreeSpaceInBuffer() < nDataSize) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_IMAGE, "Bad overflow image! %d", m_RawData.GetSize()); + FrameIsCorrupted(); + m_RawData.Reset(); + } + else + { + m_RawData.UnsafeWrite(pData, nDataSize); + } + + XN_PROFILING_END_SECTION +} + +void XnJpegToRGBImageProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnImageProcessor::OnStartOfFrame(pHeader); + m_RawData.Reset(); +} + +void XnJpegToRGBImageProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XN_PROFILING_START_SECTION("XnJpegToRGBImageProcessor::OnEndOfFrame") + +// xnOSSaveFile("c:\\temp\\fromSensor.jpeg", m_RawData.GetData(), m_RawData.GetSize()); + + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + XnUInt32 nOutputSize = pWriteBuffer->GetMaxSize(); + XnStatus nRetVal = XnStreamUncompressImageJ(&m_JPEGContext, m_RawData.GetData(), m_RawData.GetSize(), pWriteBuffer->GetUnsafeWritePointer(), &nOutputSize); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_IMAGE, "Failed to uncompress JPEG for frame %d: %s (%d)\n", GetCurrentFrameID(), xnGetStatusString(nRetVal), pWriteBuffer->GetSize()); + FrameIsCorrupted(); + + XnDumpFile* badImageDump = xnDumpFileOpen(XN_DUMP_BAD_IMAGE, "BadImage_%d.jpeg", GetCurrentFrameID()); + xnDumpFileWriteBuffer(badImageDump, m_RawData.GetData(), m_RawData.GetSize()); + xnDumpFileClose(badImageDump); + } + + pWriteBuffer->UnsafeUpdateSize(nOutputSize); + + m_RawData.Reset(); + XnImageProcessor::OnEndOfFrame(pHeader); + + XN_PROFILING_END_SECTION +} + diff --git a/Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.h new file mode 100644 index 0000000..5d3a414 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnJpegToRGBImageProcessor.h @@ -0,0 +1,58 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_JPEG_TO_RGB_IMAGE_PROCESSOR_H__ +#define __XN_JPEG_TO_RGB_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnJpegToRGBImageProcessor : public XnImageProcessor +{ +public: + XnJpegToRGBImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnJpegToRGBImageProcessor(); + + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnBuffer m_RawData; + XnStreamUncompJPEGContext m_JPEGContext; +}; + +#endif //__XN_JPEG_TO_RGB_IMAGE_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.cpp new file mode 100644 index 0000000..e210078 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.cpp @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* PrimeSense Sensor 5.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of PrimeSense Sensor * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnNesaDebugProcessor.h" +#include "XnSensor.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_SENSOR_TEC_DEBUG_MAX_PACKET_SIZE 256 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnNesaDebugProcessor::XnNesaDebugProcessor(XnDevicePrivateData* pDevicePrivateData) : + XnWholePacketProcessor(pDevicePrivateData, "NesaDebug", XN_SENSOR_TEC_DEBUG_MAX_PACKET_SIZE), + m_Dump(NULL) +{ +} + +XnNesaDebugProcessor::~XnNesaDebugProcessor() +{ + xnDumpFileClose(m_Dump); +} + +void XnNesaDebugProcessor::ProcessWholePacket(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData) +{ + if (m_Dump == NULL) + { + m_Dump = xnDumpFileOpenEx("NesaDebug", TRUE, TRUE, "NesaDebug.txt"); + } + + xnDumpFileWriteString(m_Dump, "%S\n", (XnChar*)pData); + printf("%S\n", (XnWChar*)pData); +} diff --git a/Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.h b/Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.h new file mode 100644 index 0000000..5276cc9 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnNesaDebugProcessor.h @@ -0,0 +1,52 @@ +/***************************************************************************** +* * +* PrimeSense Sensor 5.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of PrimeSense Sensor * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_NESA_DEBUG_PROCESSOR_H__ +#define __XN_NESA_DEBUG_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnWholePacketProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnNesaDebugProcessor : public XnWholePacketProcessor +{ +public: + XnNesaDebugProcessor(XnDevicePrivateData* pDevicePrivateData); + ~XnNesaDebugProcessor(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnDumpFile* m_Dump; +}; + +#endif //__XN_NESA_DEBUG_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.cpp new file mode 100644 index 0000000..3982adc --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.cpp @@ -0,0 +1,295 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnPSCompressedDepthProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnPSCompressedDepthProcessor::XnPSCompressedDepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnDepthProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnStatus XnPSCompressedDepthProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnDepthProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_RawData, GetExpectedOutputSize()); + + return XN_STATUS_OK; +} + +XnPSCompressedDepthProcessor::~XnPSCompressedDepthProcessor() +{ +} + +#define XN_CHECK_UNC_DEPTH_OUTPUT(x, y, z) \ + if (x >= y) \ + { \ + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); \ + } \ + if (z >= XN_DEVICE_SENSOR_MAX_SHIFT_VALUE) \ + { \ + z = XN_DEVICE_SENSOR_NO_DEPTH_VALUE; \ + } + +#define XN_DEPTH_OUTPUT(pDepthOutput, pOutputEnd, nValue) \ + XN_CHECK_UNC_DEPTH_OUTPUT(pDepthOutput, pOutputEnd, nValue) \ + *pDepthOutput = GetOutput(nValue); \ + ++pDepthOutput; + +#define INIT_INPUT(pInput, nInputSize) \ + const XnUInt8* __pInputOrig = pInput; \ + const XnUInt8* __pCurrInput = pInput; \ + const XnUInt8* __pInputEnd = pInput + nInputSize; \ + XnBool __bShouldReadByte = TRUE; \ + XnUInt32 __nLastByte = 0; + +#define GET_NEXT_INPUT(nInput) \ + if (__bShouldReadByte) \ + { \ + if (__pCurrInput == __pInputEnd) \ + break; \ + \ + /* read from input */ \ + __nLastByte = *__pCurrInput; \ + __bShouldReadByte = FALSE; \ + \ + /* take high 4-bits */ \ + nInput = __nLastByte >> 4; \ + \ + __pCurrInput++; \ + } \ + else \ + { \ + /* byte already read. take its low 4-bits */ \ + nInput = __nLastByte & 0x0F; \ + __bShouldReadByte = TRUE; \ + } + +/** True if input is in a steady state (not in the middle of a byte) */ +#define CAN_INPUT_STOP_HERE __bShouldReadByte + +/** Gets a pointer to n elements before current input */ +#define GET_PREV_INPUT(n) __pCurrInput - n/2; + +#define GET_INPUT_READ_BYTES (__pCurrInput - __pInputOrig); + +XnStatus XnPSCompressedDepthProcessor::UncompressDepthPS(const XnUInt8* pInput, const XnUInt32 nInputSize, + XnUInt16* pDepthOutput, XnUInt32* pnOutputSize, + XnUInt32* pnActualRead, XnBool bLastPart) +{ + // Input is made of 4-bit elements. + INIT_INPUT(pInput, nInputSize); + + XnUInt16* pOutputEnd = pDepthOutput + (*pnOutputSize / sizeof(OniDepthPixel)); + XnUInt16 nLastValue = 0; + + const XnUInt8* pInputOrig = pInput; + XnUInt16* pOutputOrig = pDepthOutput; + + const XnUInt8* pInputLastPossibleStop = pInputOrig; + XnUInt16* pOutputLastPossibleStop = pOutputOrig; + + // NOTE: we use variables of type uint32 instead of uint8 as an optimization (better CPU usage) + XnUInt32 nInput; + XnUInt32 nLargeValue; + XnBool bCanStop; + + for (;;) + { + bCanStop = CAN_INPUT_STOP_HERE; + GET_NEXT_INPUT(nInput); + + switch (nInput) + { + case 0xd: // Dummy. + // Do nothing + break; + case 0xe: // RLE + // read count + GET_NEXT_INPUT(nInput); + + // should repeat last value (nInput + 1) times + nInput++; + while (nInput != 0) + { + XN_DEPTH_OUTPUT(pDepthOutput, pOutputEnd, nLastValue); + --nInput; + } + break; + + case 0xf: // Full (or large) + // read next element + GET_NEXT_INPUT(nInput); + + // First bit tells us if it's a large diff (turned on) or a full value (turned off) + if (nInput & 0x8) // large diff (7-bit) + { + // turn off high bit, and shift left + nLargeValue = (nInput - 0x8) << 4; + + // read low 4-bits + GET_NEXT_INPUT(nInput); + + nLargeValue |= nInput; + // diff values are from -64 to 63 (0x00 to 0x7f) + nLastValue += ((XnInt16)nLargeValue - 64); + } + else // Full value (15-bit) + { + if (bCanStop) + { + // We can stop here. First input is a full value + pInputLastPossibleStop = GET_PREV_INPUT(2); + pOutputLastPossibleStop = pDepthOutput; + } + + nLargeValue = (nInput << 12); + + // read 3 more elements + GET_NEXT_INPUT(nInput); + nLargeValue |= nInput << 8; + + GET_NEXT_INPUT(nInput); + nLargeValue |= nInput << 4; + + GET_NEXT_INPUT(nInput); + nLastValue = (XnUInt16)(nLargeValue | nInput); + } + + XN_DEPTH_OUTPUT(pDepthOutput, pOutputEnd, nLastValue); + + break; + default: // all rest (smaller than 0xd) are diffs + // diff values are from -6 to 6 (0x0 to 0xc) + nLastValue += ((XnInt16)nInput - 6); + XN_DEPTH_OUTPUT(pDepthOutput, pOutputEnd, nLastValue); + } + } + + if (bLastPart == TRUE) + { + *pnOutputSize = (XnUInt32)(pDepthOutput - pOutputOrig) * sizeof(XnUInt16); + *pnActualRead = (XnUInt32)GET_INPUT_READ_BYTES; + } + else + { + *pnOutputSize = (XnUInt32)(pOutputLastPossibleStop - pOutputOrig) * sizeof(XnUInt16); + *pnActualRead = (XnUInt32)(pInputLastPossibleStop - pInputOrig) * sizeof(XnUInt8); + } + + // All is good... + return (XN_STATUS_OK); +} + +void XnPSCompressedDepthProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnPSCompressedDepthProcessor::ProcessFramePacketChunk") + + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + const XnUChar* pBuf = NULL; + XnUInt32 nBufSize = 0; + + // check if we have bytes stored from previous calls + if (m_RawData.GetSize() > 0) + { + // we have no choice. We need to append current buffer to previous bytes + if (m_RawData.GetFreeSpaceInBuffer() < nDataSize) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_DEPTH, "Bad overflow depth! %d", m_RawData.GetSize()); + FrameIsCorrupted(); + } + else + { + m_RawData.UnsafeWrite(pData, nDataSize); + } + + pBuf = m_RawData.GetData(); + nBufSize = m_RawData.GetSize(); + } + else + { + // we can process the data directly + pBuf = pData; + nBufSize = nDataSize; + } + + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + XnUInt32 nWrittenOutput = nOutputSize; + XnUInt32 nActualRead = 0; + XnBool bLastPart = pHeader->nType == XN_SENSOR_PROTOCOL_RESPONSE_DEPTH_END && (nDataOffset + nDataSize) == pHeader->nBufSize; + XnStatus nRetVal = UncompressDepthPS(pBuf, nBufSize, (XnUInt16*)pWriteBuffer->GetUnsafeWritePointer(), + &nWrittenOutput, &nActualRead, bLastPart); + + if (nRetVal != XN_STATUS_OK) + { + FrameIsCorrupted(); + + static XnUInt64 nLastPrinted = 0; + + XnUInt64 nCurrTime; + xnOSGetTimeStamp(&nCurrTime); + + if (nOutputSize != 0 || (nCurrTime - nLastPrinted) > 1000) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_DEPTH, "Uncompress depth failed: %s. Input Size: %u, Output Space: %u, Last Part: %d.", xnGetStatusString(nRetVal), nBufSize, nOutputSize, bLastPart); + + xnOSGetTimeStamp(&nLastPrinted); + } + } + + pWriteBuffer->UnsafeUpdateSize(nWrittenOutput); + + nBufSize -= nActualRead; + m_RawData.Reset(); + + // if we have any bytes left, keep them for next time + if (nBufSize > 0) + { + pBuf += nActualRead; + m_RawData.UnsafeWrite(pBuf, nBufSize); + } + + XN_PROFILING_END_SECTION +} + +void XnPSCompressedDepthProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnDepthProcessor::OnStartOfFrame(pHeader); + m_RawData.Reset(); +} + +void XnPSCompressedDepthProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnDepthProcessor::OnEndOfFrame(pHeader); + m_RawData.Reset(); +} + diff --git a/Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.h b/Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.h new file mode 100644 index 0000000..a8bab3a --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPSCompressedDepthProcessor.h @@ -0,0 +1,66 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PS_COMPRESSED_DEPTH_PROCESSOR_H__ +#define __XN_PS_COMPRESSED_DEPTH_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDepthProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnPSCompressedDepthProcessor : public XnDepthProcessor +{ +public: + XnPSCompressedDepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + virtual ~XnPSCompressedDepthProcessor(); + + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + //--------------------------------------------------------------------------- + // Internal Functions + //--------------------------------------------------------------------------- + XnStatus UncompressDepthPS(const XnUInt8* pInput, const XnUInt32 nInputSize, + XnUInt16* pDepthOutput, XnUInt32* pnOutputSize, + XnUInt32* pnActualRead, XnBool bLastPart); + +private: + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- + /* Keeps raw data in case not all bytes can be processed. */ + XnBuffer m_RawData; + + static void XN_CALLBACK_TYPE OnRequiredSizeChanged(); +}; + +#endif //__XN_PS_COMPRESSED_DEPTH_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.cpp new file mode 100644 index 0000000..42285fa --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.cpp @@ -0,0 +1,162 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnPSCompressedImageProcessor.h" +#include "Uncomp.h" +#include "YUV.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnPSCompressedImageProcessor::XnPSCompressedImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnPSCompressedImageProcessor::~XnPSCompressedImageProcessor() +{ +} + +XnStatus XnPSCompressedImageProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnImageProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_ContinuousBuffer, GetExpectedOutputSize()); + + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_YUV422: + break; + case ONI_PIXEL_FORMAT_RGB888: + XN_VALIDATE_BUFFER_ALLOCATE(m_UncompressedYUVBuffer, GetExpectedOutputSize()); + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_SENSOR_PROTOCOL_IMAGE, "Unsupported image output format: %d", GetStream()->GetOutputFormat()); + } + + return (XN_STATUS_OK); +} + +void XnPSCompressedImageProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnPSCompressedImageProcessor::ProcessFramePacketChunk") + + // if output format is YUV, we can write directly to output buffer. otherwise, we need + // to write to a temp buffer. + XnBuffer* pWriteBuffer = (GetStream()->GetOutputFormat() == ONI_PIXEL_FORMAT_YUV422) ? GetWriteBuffer() : &m_UncompressedYUVBuffer; + + const XnUChar* pBuf = NULL; + XnUInt32 nBufSize = 0; + + // check if we have bytes stored from previous calls + if (m_ContinuousBuffer.GetSize() > 0) + { + // we have no choice. We need to append current buffer to previous bytes + if (m_ContinuousBuffer.GetFreeSpaceInBuffer() < nDataSize) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_DEPTH, "Bad overflow image! %d", m_ContinuousBuffer.GetSize()); + FrameIsCorrupted(); + m_ContinuousBuffer.Reset(); + } + else + { + m_ContinuousBuffer.UnsafeWrite(pData, nDataSize); + } + + pBuf = m_ContinuousBuffer.GetData(); + nBufSize = m_ContinuousBuffer.GetSize(); + } + else + { + // we can process the data directly + pBuf = pData; + nBufSize = nDataSize; + } + + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + XnUInt32 nWrittenOutput = nOutputSize; + XnUInt32 nActualRead = 0; + XnBool bLastPart = pHeader->nType == XN_SENSOR_PROTOCOL_RESPONSE_IMAGE_END && (nDataOffset + nDataSize) == pHeader->nBufSize; + XnStatus nRetVal = XnStreamUncompressYUVImagePS(pBuf, nBufSize, pWriteBuffer->GetUnsafeWritePointer(), + &nWrittenOutput, (XnUInt16)(GetActualXRes()*2), &nActualRead, bLastPart); + + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL_IMAGE, "Image decompression failed: %s (%d of %d, requested %d, last %d)", xnGetStatusString(nRetVal), nWrittenOutput, nBufSize, nOutputSize, bLastPart); + FrameIsCorrupted(); + } + + pWriteBuffer->UnsafeUpdateSize(nWrittenOutput); + + nBufSize -= nActualRead; + m_ContinuousBuffer.Reset(); + + // if we have any bytes left, keep them for next time + if (nBufSize > 0) + { + pBuf += nActualRead; + m_ContinuousBuffer.UnsafeWrite(pBuf, nBufSize); + } + + XN_PROFILING_END_SECTION +} + +void XnPSCompressedImageProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnImageProcessor::OnStartOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} + +void XnPSCompressedImageProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XN_PROFILING_START_SECTION("XnPSCompressedImageProcessor::OnEndOfFrame") + + // if data was written to temp buffer, convert it now + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_YUV422: + break; + case ONI_PIXEL_FORMAT_RGB888: + { + XnUInt32 nActualRead = 0; + XnUInt32 nOutputSize = GetWriteBuffer()->GetFreeSpaceInBuffer(); + YUV422ToRGB888(m_UncompressedYUVBuffer.GetData(), GetWriteBuffer()->GetUnsafeWritePointer(), m_UncompressedYUVBuffer.GetSize(), &nActualRead, &nOutputSize); + GetWriteBuffer()->UnsafeUpdateSize(nOutputSize); + m_UncompressedYUVBuffer.Reset(); + } + break; + default: + assert(0); + return; + } + + XnImageProcessor::OnEndOfFrame(pHeader); + m_ContinuousBuffer.Reset(); + + XN_PROFILING_END_SECTION +} diff --git a/Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.h new file mode 100644 index 0000000..e557596 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPSCompressedImageProcessor.h @@ -0,0 +1,57 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PS_COMPRESSED_IMAGE_PROCESSOR_H__ +#define __XN_PS_COMPRESSED_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnPSCompressedImageProcessor : public XnImageProcessor +{ +public: + XnPSCompressedImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnPSCompressedImageProcessor(); + + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnBuffer m_ContinuousBuffer; + XnBuffer m_UncompressedYUVBuffer; +}; + +#endif //__XN_PS_COMPRESSED_IMAGE_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.cpp new file mode 100644 index 0000000..557402b --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.cpp @@ -0,0 +1,213 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnPacked11DepthProcessor.h" +#include +#ifdef XN_NEON +#include +#endif + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +/* The size of an input element in the stream. */ +#define XN_INPUT_ELEMENT_SIZE 11 +/* The size of an output element in the stream. */ +#define XN_OUTPUT_ELEMENT_SIZE 16 + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +/* Returns a set of bits. For example XN_ON_BITS(4) returns 0xF */ +#define XN_ON_BITS(count) ((1 << count)-1) + +/* Creates a mask of bits in offset */ +#define XN_CREATE_MASK(count, offset) (XN_ON_BITS(count) << offset) + +/* Takes the bits in offset from . +* For example: +* If we want 3 bits located in offset 2 from 0xF4: +* 11110100 +* --- +* we get 101, which is 0x5. +* and so, XN_TAKE_BITS(0xF4,3,2) == 0x5. +*/ +#define XN_TAKE_BITS(source, count, offset) ((source & XN_CREATE_MASK(count, offset)) >> offset) + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnPacked11DepthProcessor::XnPacked11DepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnDepthProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnStatus XnPacked11DepthProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnDepthProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_ContinuousBuffer, XN_INPUT_ELEMENT_SIZE); + + return (XN_STATUS_OK); +} + +XnPacked11DepthProcessor::~XnPacked11DepthProcessor() +{ +} + +XnStatus XnPacked11DepthProcessor::Unpack11to16(const XnUInt8* pcInput, const XnUInt32 nInputSize, XnUInt32* pnActualRead) +{ + const XnUInt8* pOrigInput = pcInput; + + XnUInt32 nElements = nInputSize / XN_INPUT_ELEMENT_SIZE; // floored + XnUInt32 nNeededOutput = nElements * XN_OUTPUT_ELEMENT_SIZE; + + *pnActualRead = 0; + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + // Check there is enough room for the depth pixels + if (!CheckWriteBufferForOverflow(nNeededOutput)) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + XnUInt16* pnOutput = (XnUInt16*)pWriteBuffer->GetUnsafeWritePointer(); + + XnUInt16 a0,a1,a2,a3,a4,a5,a6,a7; +#ifdef XN_NEON + XnUInt16 depth[8]; + uint16x8_t Q0; +#endif + + // Convert the 11bit packed data into 16bit shorts + for (XnUInt32 nElem = 0; nElem < nElements; ++nElem) + { + // input: 0, 1, 2,3, 4, 5, 6,7, 8, 9,10 + // -,---,---,-,---,---,---,-,---,---,- + // bits: 8,3,5,6,2,8,1,7,4,4,7,1,8,2,6,5,3,8 + // ---,---,-----,---,---,-----,---,--- + // output: 0, 1, 2, 3, 4, 5, 6, 7 + + a0 = (XN_TAKE_BITS(pcInput[0],8,0) << 3) | XN_TAKE_BITS(pcInput[1],3,5); + a1 = (XN_TAKE_BITS(pcInput[1],5,0) << 6) | XN_TAKE_BITS(pcInput[2],6,2); + a2 = (XN_TAKE_BITS(pcInput[2],2,0) << 9) | (XN_TAKE_BITS(pcInput[3],8,0) << 1) | XN_TAKE_BITS(pcInput[4],1,7); + a3 = (XN_TAKE_BITS(pcInput[4],7,0) << 4) | XN_TAKE_BITS(pcInput[5],4,4); + a4 = (XN_TAKE_BITS(pcInput[5],4,0) << 7) | XN_TAKE_BITS(pcInput[6],7,1); + a5 = (XN_TAKE_BITS(pcInput[6],1,0) << 10) | (XN_TAKE_BITS(pcInput[7],8,0) << 2) | XN_TAKE_BITS(pcInput[8],2,6); + a6 = (XN_TAKE_BITS(pcInput[8],6,0) << 5) | XN_TAKE_BITS(pcInput[9],5,3); + a7 = (XN_TAKE_BITS(pcInput[9],3,0) << 8) | XN_TAKE_BITS(pcInput[10],8,0); + + +#ifdef XN_NEON + depth[0] = GetOutput(a0); + depth[1] = GetOutput(a1); + depth[2] = GetOutput(a2); + depth[3] = GetOutput(a3); + depth[4] = GetOutput(a4); + depth[5] = GetOutput(a5); + depth[6] = GetOutput(a6); + depth[7] = GetOutput(a7); + + // Load + Q0 = vld1q_u16(depth); + // Store + vst1q_u16(pnOutput, Q0); +#else + pnOutput[0] = GetOutput(a0); + pnOutput[1] = GetOutput(a1); + pnOutput[2] = GetOutput(a2); + pnOutput[3] = GetOutput(a3); + pnOutput[4] = GetOutput(a4); + pnOutput[5] = GetOutput(a5); + pnOutput[6] = GetOutput(a6); + pnOutput[7] = GetOutput(a7); +#endif + + pcInput += XN_INPUT_ELEMENT_SIZE; + pnOutput += 8; + } + + *pnActualRead = (XnUInt32)(pcInput - pOrigInput); + pWriteBuffer->UnsafeUpdateSize(nNeededOutput); + + return XN_STATUS_OK; +} + +void XnPacked11DepthProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnPacked11DepthProcessor::ProcessFramePacketChunk") + + XnStatus nRetVal = XN_STATUS_OK; + + // check if we have data from previous packet + if (m_ContinuousBuffer.GetSize() != 0) + { + // fill in to a whole element + XnUInt32 nReadBytes = XN_MIN(nDataSize, XN_INPUT_ELEMENT_SIZE - m_ContinuousBuffer.GetSize()); + m_ContinuousBuffer.UnsafeWrite(pData, nReadBytes); + pData += nReadBytes; + nDataSize -= nReadBytes; + + if (m_ContinuousBuffer.GetSize() == XN_INPUT_ELEMENT_SIZE) + { + // process it + XnUInt32 nActualRead = 0; + Unpack11to16(m_ContinuousBuffer.GetData(), XN_INPUT_ELEMENT_SIZE, &nActualRead); + m_ContinuousBuffer.Reset(); + } + } + + // find out the number of input elements we have + XnUInt32 nActualRead = 0; + nRetVal = Unpack11to16(pData, nDataSize, &nActualRead); + if (nRetVal == XN_STATUS_OK) + { + pData += nActualRead; + nDataSize -= nActualRead; + + // if we have any bytes left, store them for next packet. + if (nDataSize > 0) + { + // no need to check for overflow. there can not be a case in which more than XN_INPUT_ELEMENT_SIZE + // are left. + m_ContinuousBuffer.UnsafeWrite(pData, nDataSize); + } + } + + XN_PROFILING_END_SECTION +} + +void XnPacked11DepthProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnDepthProcessor::OnStartOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} + +void XnPacked11DepthProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnDepthProcessor::OnEndOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} diff --git a/Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.h b/Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.h new file mode 100644 index 0000000..58d500f --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPacked11DepthProcessor.h @@ -0,0 +1,62 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PACKED_11_DEPTH_PROCESSOR_H__ +#define __XN_PACKED_11_DEPTH_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDepthProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnPacked11DepthProcessor : public XnDepthProcessor +{ +public: + XnPacked11DepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + virtual ~XnPacked11DepthProcessor(); + + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + //--------------------------------------------------------------------------- + // Internal Functions + //--------------------------------------------------------------------------- + XnStatus Unpack11to16(const XnUInt8* pcInput, const XnUInt32 nInputSize, XnUInt32* pnActualRead); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + /* A buffer used for storing some left-over bytes for the next packet. */ + XnBuffer m_ContinuousBuffer; +}; + +#endif //__XN_PACKED_11_DEPTH_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.cpp new file mode 100644 index 0000000..7d0aed9 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.cpp @@ -0,0 +1,316 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnPacked12DepthProcessor.h" +#include +#ifdef XN_NEON +#include +#endif + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +/* The size of an input element in the stream. */ +#define XN_INPUT_ELEMENT_SIZE 24 +/* The size of an output element in the stream. */ +#define XN_OUTPUT_ELEMENT_SIZE 32 + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +/* Returns a set of bits. For example XN_ON_BITS(4) returns 0xF */ +#define XN_ON_BITS(count) ((1 << count)-1) + +/* Creates a mask of bits in offset */ +#define XN_CREATE_MASK(count, offset) (XN_ON_BITS(count) << offset) + +/* Takes the bits in offset from . +* For example: +* If we want 3 bits located in offset 2 from 0xF4: +* 11110100 +* --- +* we get 101, which is 0x5. +* and so, XN_TAKE_BITS(0xF4,3,2) == 0x5. +*/ +#define XN_TAKE_BITS(source, count, offset) ((source & XN_CREATE_MASK(count, offset)) >> offset) + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnPacked12DepthProcessor::XnPacked12DepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnDepthProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnStatus XnPacked12DepthProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnDepthProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_ContinuousBuffer, XN_INPUT_ELEMENT_SIZE); + + return (XN_STATUS_OK); +} + +XnPacked12DepthProcessor::~XnPacked12DepthProcessor() +{ +} + +XnStatus XnPacked12DepthProcessor::Unpack12to16(const XnUInt8* pcInput, const XnUInt32 nInputSize, XnUInt32* pnActualRead) +{ + const XnUInt8* pOrigInput = pcInput; + + XnUInt32 nElements = nInputSize / XN_INPUT_ELEMENT_SIZE; // floored + XnUInt32 nNeededOutput = nElements * XN_OUTPUT_ELEMENT_SIZE; + + *pnActualRead = 0; + XnBuffer* pWriteBuffer = GetWriteBuffer(); + if (!CheckWriteBufferForOverflow(nNeededOutput)) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + XnUInt16* pnOutput = (XnUInt16*)pWriteBuffer->GetUnsafeWritePointer(); + XnUInt16 shift[16]; +#ifdef XN_NEON + XnUInt16 depth[16]; + uint8x8x3_t inD3; + uint8x8_t rshft4D, lshft4D; + uint16x8_t rshft4Q, lshft4Q; + uint16x8_t depthQ; + uint16x8x2_t shiftQ2; +#endif + + // Convert the 11bit packed data into 16bit shorts + for (XnUInt32 nElem = 0; nElem < nElements; ++nElem) + { +#ifndef XN_NEON + // input: 0, 1,2,3, 4,5,6, 7,8,9, 10,11,12, 13,14,15, 16,17,18, 19,20,21, 22,23 + // -,---,-,-,---,-,-,---,-,-,---,--,--,---,--,--,---,--,--,---,--,--,---,-- + // bits: 8,4,4,8,8,4,4,8,8,4,4,8,8,4,4, 8, 8,4,4, 8, 8,4,4, 8, 8,4,4, 8, 8,4,4, 8 + // ---,---,---,---,---,---,---,----,----,----,----,----,----,----,----,---- + // output: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + + shift[0] = (XN_TAKE_BITS(pcInput[0],8,0) << 4) | XN_TAKE_BITS(pcInput[1],4,4); + shift[1] = (XN_TAKE_BITS(pcInput[1],4,0) << 8) | XN_TAKE_BITS(pcInput[2],8,0); + shift[2] = (XN_TAKE_BITS(pcInput[3],8,0) << 4) | XN_TAKE_BITS(pcInput[4],4,4); + shift[3] = (XN_TAKE_BITS(pcInput[4],4,0) << 8) | XN_TAKE_BITS(pcInput[5],8,0); + shift[4] = (XN_TAKE_BITS(pcInput[6],8,0) << 4) | XN_TAKE_BITS(pcInput[7],4,4); + shift[5] = (XN_TAKE_BITS(pcInput[7],4,0) << 8) | XN_TAKE_BITS(pcInput[8],8,0); + shift[6] = (XN_TAKE_BITS(pcInput[9],8,0) << 4) | XN_TAKE_BITS(pcInput[10],4,4); + shift[7] = (XN_TAKE_BITS(pcInput[10],4,0) << 8) | XN_TAKE_BITS(pcInput[11],8,0); + shift[8] = (XN_TAKE_BITS(pcInput[12],8,0) << 4) | XN_TAKE_BITS(pcInput[13],4,4); + shift[9] = (XN_TAKE_BITS(pcInput[13],4,0) << 8) | XN_TAKE_BITS(pcInput[14],8,0); + shift[10] = (XN_TAKE_BITS(pcInput[15],8,0) << 4) | XN_TAKE_BITS(pcInput[16],4,4); + shift[11] = (XN_TAKE_BITS(pcInput[16],4,0) << 8) | XN_TAKE_BITS(pcInput[17],8,0); + shift[12] = (XN_TAKE_BITS(pcInput[18],8,0) << 4) | XN_TAKE_BITS(pcInput[19],4,4); + shift[13] = (XN_TAKE_BITS(pcInput[19],4,0) << 8) | XN_TAKE_BITS(pcInput[20],8,0); + shift[14] = (XN_TAKE_BITS(pcInput[21],8,0) << 4) | XN_TAKE_BITS(pcInput[22],4,4); + shift[15] = (XN_TAKE_BITS(pcInput[22],4,0) << 8) | XN_TAKE_BITS(pcInput[23],8,0); + + shift[0] = (((shift[0]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[0]) : 0); + shift[1] = (((shift[1]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[1]) : 0); + shift[2] = (((shift[2]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[2]) : 0); + shift[3] = (((shift[3]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[3]) : 0); + shift[4] = (((shift[4]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[4]) : 0); + shift[5] = (((shift[5]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[5]) : 0); + shift[6] = (((shift[6]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[6]) : 0); + shift[7] = (((shift[7]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[7]) : 0); + shift[8] = (((shift[8]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[8]) : 0); + shift[9] = (((shift[9]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[9]) : 0); + shift[10] = (((shift[10]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[10]) : 0); + shift[11] = (((shift[11]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[11]) : 0); + shift[12] = (((shift[12]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[12]) : 0); + shift[13] = (((shift[13]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[13]) : 0); + shift[14] = (((shift[14]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[14]) : 0); + shift[15] = (((shift[15]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[15]) : 0); + + pnOutput[0] = GetOutput(shift[0]); + pnOutput[1] = GetOutput(shift[1]); + pnOutput[2] = GetOutput(shift[2]); + pnOutput[3] = GetOutput(shift[3]); + pnOutput[4] = GetOutput(shift[4]); + pnOutput[5] = GetOutput(shift[5]); + pnOutput[6] = GetOutput(shift[6]); + pnOutput[7] = GetOutput(shift[7]); + pnOutput[8] = GetOutput(shift[8]); + pnOutput[9] = GetOutput(shift[9]); + pnOutput[10] = GetOutput(shift[10]); + pnOutput[11] = GetOutput(shift[11]); + pnOutput[12] = GetOutput(shift[12]); + pnOutput[13] = GetOutput(shift[13]); + pnOutput[14] = GetOutput(shift[14]); + pnOutput[15] = GetOutput(shift[15]); + +#else + // input: 0, 1,2 (X8) + // -,---,- + // bits: 8,4,4,8 (X8) + // ---,--- + // output: 0, 1 (X8) + + // Split 24 bytes into 3 vectors (64 bit each) + inD3 = vld3_u8(pcInput); + + // rshft4D0 contains 4 MSB of second vector (placed at offset 0) + rshft4D = vshr_n_u8(inD3.val[1], 4); + // lshft4D0 contains 4 LSB of second vector (placed at offset 4) + lshft4D = vshl_n_u8(inD3.val[1], 4); + + // Expand 64 bit vectors to 128 bit (8 values of 16 bits) + shiftQ2.val[0] = vmovl_u8(inD3.val[0]); + shiftQ2.val[1] = vmovl_u8(inD3.val[2]); + rshft4Q = vmovl_u8(rshft4D); + lshft4Q = vmovl_u8(lshft4D); + + // Even indexed shift = 8 bits from first vector + 4 MSB bits of second vector + shiftQ2.val[0] = vshlq_n_u16(shiftQ2.val[0], 4); + shiftQ2.val[0] = vorrq_u16(shiftQ2.val[0], rshft4Q); + + // Odd indexed shift = 4 LSB bits of second vector + 8 bits from third vector + lshft4Q = vshlq_n_u16(lshft4Q, 4); + shiftQ2.val[1] = vorrq_u16(shiftQ2.val[1], lshft4Q); + + // Interleave shift values to a single vector + vst2q_u16(shift, shiftQ2); + + shift[0] = (((shift[0]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[0]) : 0); + shift[1] = (((shift[1]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[1]) : 0); + shift[2] = (((shift[2]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[2]) : 0); + shift[3] = (((shift[3]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[3]) : 0); + shift[4] = (((shift[4]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[4]) : 0); + shift[5] = (((shift[5]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[5]) : 0); + shift[6] = (((shift[6]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[6]) : 0); + shift[7] = (((shift[7]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[7]) : 0); + shift[8] = (((shift[8]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[8]) : 0); + shift[9] = (((shift[9]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[9]) : 0); + shift[10] = (((shift[10]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[10]) : 0); + shift[11] = (((shift[11]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[11]) : 0); + shift[12] = (((shift[12]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[12]) : 0); + shift[13] = (((shift[13]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[13]) : 0); + shift[14] = (((shift[14]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[14]) : 0); + shift[15] = (((shift[15]) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (shift[15]) : 0); + + depth[0] = GetOutput(shift[0]); + depth[1] = GetOutput(shift[1]); + + depth[2] = GetOutput(shift[2]); + depth[3] = GetOutput(shift[3]); + + depth[4] = GetOutput(shift[4]); + depth[5] = GetOutput(shift[5]); + + depth[6] = GetOutput(shift[6]); + depth[7] = GetOutput(shift[7]); + + // Load + depthQ = vld1q_u16(depth); + //Store + vst1q_u16(pnOutput, depthQ); + + depth[8] = GetOutput(shift[8]); + depth[9] = GetOutput(shift[9]); + + depth[10] = GetOutput(shift[10]); + depth[11] = GetOutput(shift[11]); + + depth[12] = GetOutput(shift[12]); + depth[13] = GetOutput(shift[13]); + + depth[14] = GetOutput(shift[14]); + depth[15] = GetOutput(shift[15]); + + // Load + depthQ = vld1q_u16(depth + 8); + // Store + vst1q_u16(pnOutput + 8, depthQ); + +#endif + + pcInput += XN_INPUT_ELEMENT_SIZE; + pnOutput += 16; + } + + *pnActualRead = (XnUInt32)(pcInput - pOrigInput); + pWriteBuffer->UnsafeUpdateSize(nNeededOutput); + + return XN_STATUS_OK; +} + +void XnPacked12DepthProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnPacked12DepthProcessor::ProcessFramePacketChunk") + + XnStatus nRetVal = XN_STATUS_OK; + + // check if we have data from previous packet + if (m_ContinuousBuffer.GetSize() != 0) + { + // fill in to a whole element + XnUInt32 nReadBytes = XN_MIN(nDataSize, XN_INPUT_ELEMENT_SIZE - m_ContinuousBuffer.GetSize()); + m_ContinuousBuffer.UnsafeWrite(pData, nReadBytes); + pData += nReadBytes; + nDataSize -= nReadBytes; + + if (m_ContinuousBuffer.GetSize() == XN_INPUT_ELEMENT_SIZE) + { + // process it + XnUInt32 nActualRead = 0; + Unpack12to16(m_ContinuousBuffer.GetData(), XN_INPUT_ELEMENT_SIZE, &nActualRead); + m_ContinuousBuffer.Reset(); + } + } + + // find out the number of input elements we have + XnUInt32 nActualRead = 0; + nRetVal = Unpack12to16(pData, nDataSize, &nActualRead); + if (nRetVal == XN_STATUS_OK) + { + pData += nActualRead; + nDataSize -= nActualRead; + + // if we have any bytes left, store them for next packet. + if (nDataSize > 0) + { + // no need to check for overflow. there can not be a case in which more than XN_INPUT_ELEMENT_SIZE + // are left. + m_ContinuousBuffer.UnsafeWrite(pData, nDataSize); + } + } + + XN_PROFILING_END_SECTION +} + +void XnPacked12DepthProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnDepthProcessor::OnStartOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} + +void XnPacked12DepthProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnDepthProcessor::OnEndOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} diff --git a/Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.h b/Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.h new file mode 100644 index 0000000..781214c --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPacked12DepthProcessor.h @@ -0,0 +1,62 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_PACKED_12_DEPTH_PROCESSOR_H__ +#define __XN_PACKED_12_DEPTH_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDepthProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnPacked12DepthProcessor : public XnDepthProcessor +{ +public: + XnPacked12DepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + virtual ~XnPacked12DepthProcessor(); + + XnStatus Init(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + //--------------------------------------------------------------------------- + // Internal Functions + //--------------------------------------------------------------------------- + XnStatus Unpack12to16(const XnUInt8* pcInput, const XnUInt32 nInputSize, XnUInt32* pnActualRead); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + /* A buffer used for storing some left-over bytes for the next packet. */ + XnBuffer m_ContinuousBuffer; +}; + +#endif //__XN_PACKED_12_DEPTH_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnParams.h b/Source/Drivers/PS1080/Sensor/XnParams.h new file mode 100644 index 0000000..b03ad99 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnParams.h @@ -0,0 +1,134 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef XN_PARAMS_H +#define XN_PARAMS_H + +typedef enum +{ + //General, + PARAM_GENERAL_CURRENT_MODE = 0, + PARAM_GENERAL_FRAME_SYNC = 1, + PARAM_GENERAL_REGISTRATION_ENABLE = 2, + PARAM_GENERAL_STREAM_PRIORITY = 3, + PARAM_GENERAL_TRIGGER_ACTION = 4, + PARAM_GENERAL_STREAM0_MODE = 5, + PARAM_GENERAL_STREAM1_MODE = 6, + //Audio, + PARAM_GENERAL_STREAM2_MODE = 7, + PARAM_AUDIO_STEREO_MODE = 8, + PARAM_AUDIO_SAMPLE_RATE = 9, + PARAM_AUDIO_LEFT_CHANNEL_VOLUME_LEVEL = 10, + PARAM_AUDIO_RIGHT_CHANNEL_VOLUME_LEVEL = 11, + //Image, + PARAM_IMAGE_FORMAT = 12, + PARAM_IMAGE_RESOLUTION = 13, + PARAM_IMAGE_FPS = 14, + PARAM_IMAGE_AGC = 15, + PARAM_IMAGE_QUALITY = 16, + PARAM_IMAGE_FLICKER_DETECTION = 17, + //Depth, + PARAM_DEPTH_FORMAT = 18, + PARAM_DEPTH_RESOLUTION = 19, + PARAM_DEPTH_FPS = 20, + PARAM_DEPTH_AGC = 21, + PARAM_DEPTH_HOLE_FILTER = 22, + PARAM_DEPTH_MIRROR = 23, + PARAM_DEPTH_DECIMATION = 24, + //IR, + PARAM_IR_FORMAT = 25, + PARAM_IR_RESOLUTION = 26, + PARAM_IR_FPS = 27, + PARAM_IR_AGC = 28, + PARAM_IR_QUALITY = 29, + //Misc, + PARAM_MISC_LOG_FILTER = 30, + PARAM_MISC_PACKET_TIMEOUT = 31, + PARAM_MISC_PS_SESSION_TIMEOUT = 32, + PARAM_AUDIO_LEFT_CHANNEL_MUTE = 33, + PARAM_AUDIO_RIGHT_CHANNEL_MUTE = 34, + PARAM_AUDIO_MICROPHONE_IN = 35, + PARAM_DEPTH_GMC_MODE = 36, + PARAM_DEPTH_GMC_REF_OFFSET = 37, + PARAM_DEPTH_GMC_RICC = 38, + PARAM_DEPTH_GMC_DX_CORRECTION = 39, + PARAM_DEPTH_GMC_DX_CORRECTION_A = 40, + PARAM_DEPTH_GMC_DX_CORRECTION_B_HIGH = 41, + PARAM_DEPTH_GMC_DX_CORRECTION_B_LOW = 42, + PARAM_DEPTH_GMC_DX_CORRECTION_DB_HIGH = 43, + PARAM_DEPTH_GMC_DX_CORRECTION_DB_LOW = 44, + PARAM_DEPTH_WHITE_BALANCE_ENABLE = 45, + + //Image Crop + PARAM_IMAGE_CROP_SIZE_X = 46, + PARAM_IMAGE_CROP_SIZE_Y = 47, + PARAM_IMAGE_CROP_OFFSET_X = 48, + PARAM_IMAGE_CROP_OFFSET_Y = 49, + PARAM_IMAGE_CROP_MODE = 50, + //Depth Crop + PARAM_DEPTH_CROP_SIZE_X = 51, + PARAM_DEPTH_CROP_SIZE_Y = 52, + + PARAM_DEPTH_CROP_OFFSET_X = 53, + PARAM_DEPTH_CROP_OFFSET_Y = 54, + PARAM_DEPTH_CROP_MODE = 55, + //IR Crop + PARAM_IR_CROP_SIZE_X = 56, + PARAM_IR_CROP_SIZE_Y = 57, + PARAM_IR_CROP_OFFSET_X = 58, + PARAM_IR_CROP_OFFSET_Y = 59, + PARAM_IR_CROP_MODE = 60, + + PARAM_GMC_DEBUG = 61, + PARAM_APC_ENABLE = 62, + + PARAM_DEPTH_AGC_BIN0_LOW = 63, + PARAM_DEPTH_AGC_BIN0_HIGH = 64, + PARAM_DEPTH_AGC_BIN1_LOW = 65, + PARAM_DEPTH_AGC_BIN1_HIGH = 66, + PARAM_DEPTH_AGC_BIN2_LOW = 67, + PARAM_DEPTH_AGC_BIN2_HIGH = 68, + PARAM_DEPTH_AGC_BIN3_LOW = 69, + PARAM_DEPTH_AGC_BIN3_HIGH = 70, + + PARAM_IMAGE_MIRROR = 71, + PARAM_IR_MIRROR = 72, + PARAM_WAVELENGTH_CORRECTION_ENABLED = 73, + PARAM_WAVELENGTH_CORRECTION_DEBUG_ENABLED = 74, + PARAM_GMC_CORRECTION_MODE = 75, + PARAM_IMAGE_SHARPNESS = 76, + PARAM_IMAGE_AUTO_WHITE_BALANCE_MODE = 77, + PARAM_IMAGE_COLOR_TEMPERATURE = 78, + PARAM_IMAGE_BACK_LIGHT_COMPENSATION = 79, + PARAM_IMAGE_AUTO_EXPOSURE_MODE = 80, + PARAM_IMAGE_EXPOSURE_BAR = 81, + PARAM_IMAGE_LOW_LIGHT_COMPENSATION_MODE = 82, + PARAM_DEPTH_CLOSE_RANGE = 84, + PARAM_FILE_SYSTEM_LOCK = 85, +} EConfig_Params; + +typedef enum XnExecuter +{ + XN_EXECUTER_NONE = 0, + XN_EXECUTER_FW = 1, + XN_EXECUTER_HOST = 2, +} XnExecuter; + +#endif diff --git a/Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.cpp new file mode 100644 index 0000000..0e15a10 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.cpp @@ -0,0 +1,54 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnPassThroughImageProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnPassThroughImageProcessor::XnPassThroughImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnPassThroughImageProcessor::~XnPassThroughImageProcessor() +{ +} + +void XnPassThroughImageProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnUncompressedYUVImageProcessor::ProcessFramePacketChunk") + + // when image is uncompressed, we can just copy it directly to write buffer + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + // make sure we have enough room + if (CheckWriteBufferForOverflow(nDataSize)) + { + pWriteBuffer->UnsafeWrite(pData, nDataSize); + } + + XN_PROFILING_END_SECTION +} diff --git a/Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.h new file mode 100644 index 0000000..b2d48f5 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnPassThroughImageProcessor.h @@ -0,0 +1,46 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_UNCOMPRESSED_YUV_IMAGE_PROCESSOR_H__ +#define __XN_UNCOMPRESSED_YUV_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnPassThroughImageProcessor : public XnImageProcessor +{ +public: + XnPassThroughImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnPassThroughImageProcessor(); + + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); +}; + +#endif //__XN_UNCOMPRESSED_YUV_IMAGE_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnSensor.cpp b/Source/Drivers/PS1080/Sensor/XnSensor.cpp new file mode 100644 index 0000000..7f74a40 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensor.cpp @@ -0,0 +1,1959 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnSensor.h" + +#include "XnSensorDepthStream.h" +#include "XnSensorImageStream.h" +#include "XnSensorIRStream.h" +#include "XnSensorAudioStream.h" +#include "XnDeviceSensor.h" +#include "XnHostProtocol.h" +#include "XnDeviceSensorInit.h" +#include "XnDeviceEnumeration.h" +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_SENSOR_MAX_STREAM_COUNT 5 +#define XN_SENSOR_FRAME_SYNC_MAX_DIFF 3 +#define XN_SENSOR_DEFAULT_CLOSE_STREAMS_ON_SHUTDOWN TRUE +#define XN_SENSOR_DEFAULT_HOST_TIMESTAMPS FALSE +#define XN_GLOBAL_CONFIG_FILE_NAME "PS1080.ini" + +#define FRAME_SYNC_MAX_FRAME_TIME_DIFF 3000 + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + #define XN_SENSOR_DEFAULT_USB_INTERFACE XN_SENSOR_USB_INTERFACE_ISO_ENDPOINTS +#else + // on weak platforms (Arm), we prefer to use BULK + // BULK seems to be more stable on linux + #define XN_SENSOR_DEFAULT_USB_INTERFACE XN_SENSOR_USB_INTERFACE_BULK_ENDPOINTS +#endif + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef struct XnWaitForSycnhedFrameData +{ + XnSensor* pThis; + const XnChar* strDepthStream; + const XnChar* strImageStream; +} XnWaitForSycnhedFrameData; + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnSensor::XnSensor(XnBool bResetOnStartup /* = TRUE */, XnBool bLeanInit /* = FALSE */) : + XnDeviceBase(), + m_hDisconnectedCallback(NULL), + m_ErrorState(XN_MODULE_PROPERTY_ERROR_STATE, "ErrorState", XN_STATUS_OK), + m_ResetSensorOnStartup(XN_MODULE_PROPERTY_RESET_SENSOR_ON_STARTUP, "ResetOnStartup", bResetOnStartup), + m_LeanInit(XN_MODULE_PROPERTY_LEAN_INIT, "LeanInit", bLeanInit), + m_Interface(XN_MODULE_PROPERTY_USB_INTERFACE, "UsbInterface", bResetOnStartup ? XN_SENSOR_DEFAULT_USB_INTERFACE : XN_SENSOR_USB_INTERFACE_DEFAULT), + m_ReadData(0, "ReadData", FALSE), + m_FrameSync(XN_MODULE_PROPERTY_FRAME_SYNC, "FrameSync", FALSE), + m_FirmwareFrameSync(XN_MODULE_PROPERTY_FIRMWARE_FRAME_SYNC, "FirmwareFrameSync", FALSE), + m_CloseStreamsOnShutdown(XN_MODULE_PROPERTY_CLOSE_STREAMS_ON_SHUTDOWN, "CloseStreamsOnShutdown", XN_SENSOR_DEFAULT_CLOSE_STREAMS_ON_SHUTDOWN), + m_HostTimestamps(XN_MODULE_PROPERTY_HOST_TIMESTAMPS, "HostTimestamps", XN_SENSOR_DEFAULT_HOST_TIMESTAMPS), + m_FirmwareParam(XN_MODULE_PROPERTY_FIRMWARE_PARAM, "FirmwareParam", NULL), + m_CmosBlankingUnits(XN_MODULE_PROPERTY_CMOS_BLANKING_UNITS, "BlankingUnits", NULL), + m_CmosBlankingTime(XN_MODULE_PROPERTY_CMOS_BLANKING_TIME, "BlankingTime", NULL), + m_Reset(XN_MODULE_PROPERTY_RESET, "Reset"), + m_Version(XN_MODULE_PROPERTY_VERSION, "Version", &m_DevicePrivateData.Version, sizeof(m_DevicePrivateData.Version), NULL), + m_FixedParam(XN_MODULE_PROPERTY_FIXED_PARAMS, "FixedParams", NULL), + m_ID(XN_MODULE_PROPERTY_SERIAL_NUMBER, "ID"), + m_DeviceName(XN_MODULE_PROPERTY_PHYSICAL_DEVICE_NAME, "PhysicalDeviceName"), + m_VendorSpecificData(XN_MODULE_PROPERTY_VENDOR_SPECIFIC_DATA, "VendorData"), + m_PlatformString(XN_MODULE_PROPERTY_SENSOR_PLATFORM_STRING, "SensorPlatformString"), + m_AudioSupported(XN_MODULE_PROPERTY_AUDIO_SUPPORTED, "IsAudioSupported"), + m_ImageSupported(XN_MODULE_PROPERTY_IMAGE_SUPPORTED, "IsImageSupported"), + m_ImageControl(XN_MODULE_PROPERTY_IMAGE_CONTROL, "ImageControl", NULL), + m_DepthControl(XN_MODULE_PROPERTY_DEPTH_CONTROL, "DepthControl", NULL), + m_AHB(XN_MODULE_PROPERTY_AHB, "AHB", NULL), + m_LedState(XN_MODULE_PROPERTY_LED_STATE, "LedState", NULL), + m_EmitterEnabled(XN_MODULE_PROPERTY_EMITTER_STATE, "EmitterState", NULL), + m_FirmwareLogFilter(XN_MODULE_PROPERTY_FIRMWARE_LOG_FILTER, "FirmwareLogFilter", 0), + m_FirmwareLogInterval(XN_MODULE_PROPERTY_FIRMWARE_LOG_INTERVAL, "FirmwareLogInterval", 0), + m_FirmwareLogPrint(XN_MODULE_PROPERTY_PRINT_FIRMWARE_LOG, "PrintFirmwareLog", FALSE), + m_FirmwareCPUInterval(XN_MODULE_PROPERTY_FIRMWARE_CPU_INTERVAL, "FirmwareCPUInterval", 0), + m_APCEnabled(XN_MODULE_PROPERTY_APC_ENABLED, "APCEnabled", TRUE), + m_FirmwareTecDebugPrint(XN_MODULE_PROPERTY_FIRMWARE_TEC_DEBUG_PRINT, "TecDebugPrint", FALSE), + m_I2C(XN_MODULE_PROPERTY_I2C, "I2C", NULL), + m_DeleteFile(XN_MODULE_PROPERTY_DELETE_FILE, "DeleteFile"), + m_TecSetPoint(XN_MODULE_PROPERTY_TEC_SET_POINT, "TecSetPoint"), + m_TecStatus(XN_MODULE_PROPERTY_TEC_STATUS, "TecStatus", NULL), + m_TecFastConvergenceStatus(XN_MODULE_PROPERTY_TEC_FAST_CONVERGENCE_STATUS, "TecFastConvergenceStatus", NULL), + m_EmitterSetPoint(XN_MODULE_PROPERTY_EMITTER_SET_POINT, "EmitterSetPoint"), + m_EmitterStatus(XN_MODULE_PROPERTY_EMITTER_STATUS, "EmitterStatus", NULL), + m_FileAttributes(XN_MODULE_PROPERTY_FILE_ATTRIBUTES, "FileAttributes", NULL), + m_FlashFile(XN_MODULE_PROPERTY_FILE, "File", NULL), + m_FirmwareLog(XN_MODULE_PROPERTY_FIRMWARE_LOG, "FirmwareLog", NULL), + m_FlashChunk(XN_MODULE_PROPERTY_FLASH_CHUNK, "FlashChunk", NULL), + m_FileList(XN_MODULE_PROPERTY_FILE_LIST, "FileList", NULL), + m_BIST(XN_MODULE_PROPERTY_BIST, "BIST", NULL), + m_ProjectorFault(XN_MODULE_PROPERTY_PROJECTOR_FAULT, "ProjectorFault", NULL), + m_Firmware(&m_DevicePrivateData), + m_FPS(), + m_CmosInfo(&m_Firmware, &m_DevicePrivateData), + m_SensorIO(&m_DevicePrivateData.SensorHandle), + m_Objects(&m_Firmware, &m_DevicePrivateData, &m_FPS, &m_CmosInfo), + m_pScheduler(NULL), + m_pLogTask(NULL), + m_pCPUTask(NULL), + m_FirmwareLogDump(NULL), + m_FrameSyncDump(NULL), + m_bInitialized(FALSE) +{ + // reset all data + xnOSMemSet(&m_DevicePrivateData, 0, sizeof(XnDevicePrivateData)); + ResolveGlobalConfigFileName(m_strGlobalConfigFile, sizeof(m_strGlobalConfigFile), NULL); + + m_ResetSensorOnStartup.UpdateSetCallbackToDefault(); + m_LeanInit.UpdateSetCallbackToDefault(); + m_Interface.UpdateSetCallback(SetInterfaceCallback, this); + m_ReadData.UpdateSetCallback(SetReadDataCallback, this); + m_FrameSync.UpdateSetCallbackToDefault(); + m_FirmwareFrameSync.UpdateSetCallback(SetFirmwareFrameSyncCallback, this); + m_FirmwareParam.UpdateSetCallback(SetFirmwareParamCallback, this); + m_FirmwareParam.UpdateGetCallback(GetFirmwareParamCallback, this); + m_CmosBlankingUnits.UpdateSetCallback(SetCmosBlankingUnitsCallback, this); + m_CmosBlankingUnits.UpdateGetCallback(GetCmosBlankingUnitsCallback, this); + m_CmosBlankingTime.UpdateSetCallback(SetCmosBlankingTimeCallback, this); + m_CmosBlankingTime.UpdateGetCallback(GetCmosBlankingTimeCallback, this); + m_Reset.UpdateSetCallback(ResetCallback, this); + m_FixedParam.UpdateGetCallback(GetFixedParamsCallback, this); + m_CloseStreamsOnShutdown.UpdateSetCallbackToDefault(); + m_HostTimestamps.UpdateSetCallbackToDefault(); + m_AudioSupported.UpdateGetCallback(GetAudioSupportedCallback, this); + m_ImageSupported.UpdateGetCallback(GetImageSupportedCallback, this); + m_ImageControl.UpdateSetCallback(SetImageCmosRegisterCallback, this); + m_ImageControl.UpdateGetCallback(GetImageCmosRegisterCallback, this); + m_DepthControl.UpdateSetCallback(SetDepthCmosRegisterCallback, this); + m_DepthControl.UpdateGetCallback(GetDepthCmosRegisterCallback, this); + m_AHB.UpdateSetCallback(WriteAHBCallback, this); + m_AHB.UpdateGetCallback(ReadAHBCallback, this); + m_LedState.UpdateSetCallback(SetLedStateCallback, this); + m_EmitterEnabled.UpdateSetCallback(SetEmitterStateCallback, this); + m_FirmwareLogInterval.UpdateSetCallback(SetFirmwareLogIntervalCallback, this); + m_FirmwareLogPrint.UpdateSetCallback(SetFirmwareLogPrintCallback, this); + m_FirmwareCPUInterval.UpdateSetCallback(SetFirmwareCPUIntervalCallback, this); + m_DeleteFile.UpdateSetCallback(DeleteFileCallback, this); + m_FirmwareLogFilter.UpdateSetCallback(SetFirmwareLogFilterCallback, this); + m_APCEnabled.UpdateSetCallback(SetAPCEnabledCallback, this); + m_TecSetPoint.UpdateSetCallback(SetTecSetPointCallback, this); + m_TecStatus.UpdateGetCallback(GetTecStatusCallback, this); + m_TecFastConvergenceStatus.UpdateGetCallback(GetTecFastConvergenceStatusCallback, this); + m_EmitterSetPoint.UpdateSetCallback(SetEmitterSetPointCallback, this); + m_EmitterStatus.UpdateGetCallback(GetEmitterStatusCallback, this); + m_I2C.UpdateSetCallback(SetI2CCallback, this); + m_I2C.UpdateGetCallback(GetI2CCallback, this); + m_FileAttributes.UpdateSetCallback(SetFileAttributesCallback, this); + m_FlashFile.UpdateSetCallback(WriteFlashFileCallback, this); + m_FlashFile.UpdateGetCallback(ReadFlashFileCallback, this); + m_FirmwareLog.UpdateGetCallback(GetFirmwareLogCallback, this); + m_FlashChunk.UpdateGetCallback(ReadFlashChunkCallback, this); + m_FileList.UpdateGetCallback(GetFileListCallback, this); + m_BIST.UpdateSetCallback(RunBISTCallback, this); + m_ProjectorFault.UpdateSetCallback(SetProjectorFaultCallback, this); + m_FirmwareTecDebugPrint.UpdateSetCallbackToDefault(); + + // Clear the frame-synced streams. + m_nFrameSyncEnabled = FALSE; + m_nFrameSyncLastFrameID = 0; +} + +XnSensor::~XnSensor() +{ + XnSensor::Destroy(); +} + +XnStatus XnSensor::InitImpl(const XnDeviceConfig *pDeviceConfig) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Initializing device sensor..."); + + nRetVal = xnSchedulerStart(&m_pScheduler); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_PropSynchronizer.RegisterSynchronization(&m_Firmware.GetParams()->m_APCEnabled, &m_APCEnabled); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_PropSynchronizer.RegisterSynchronization(&m_Firmware.GetParams()->m_LogFilter, &m_FirmwareLogFilter); + XN_IS_STATUS_OK(nRetVal); + + // Frame Sync + XnCallbackHandle hCallbackDummy; + nRetVal = m_FrameSync.OnChangeEvent().Register(FrameSyncPropertyChangedCallback, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetFirmware()->GetParams()->m_Stream0Mode.OnChangeEvent().Register(FrameSyncPropertyChangedCallback, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetFirmware()->GetParams()->m_Stream1Mode.OnChangeEvent().Register(FrameSyncPropertyChangedCallback, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + // other stuff + m_FrameSyncDump = xnDumpFileOpen(XN_DUMP_FRAME_SYNC, "FrameSync.csv"); + xnDumpFileWriteString(m_FrameSyncDump, "HostTime(us),DepthNewData,DepthTimestamp(ms),ImageNewData,ImageTimestamp(ms),Diff(ms),Action\n"); + + nRetVal = XnDeviceBase::InitImpl(pDeviceConfig); + XN_IS_STATUS_OK(nRetVal); + + // now that everything is configured, open the sensor + nRetVal = InitSensor(pDeviceConfig); + if (nRetVal != XN_STATUS_OK) + { + Destroy(); + return (nRetVal); + } + + nRetVal = XnDeviceEnumeration::DisconnectedEvent().Register(OnDeviceDisconnected, this, m_hDisconnectedCallback); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_DEVICE_SENSOR, "Device sensor initialized"); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::InitSensor(const XnDeviceConfig* pDeviceConfig) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnDevicePrivateData* pDevicePrivateData = GetDevicePrivateData(); + + pDevicePrivateData->pSensor = this; + + // open IO + nRetVal = m_SensorIO.OpenDevice(pDeviceConfig->cpConnectionString); + XN_IS_STATUS_OK(nRetVal); + + // initialize + nRetVal = XnDeviceSensorInit(pDevicePrivateData); + XN_IS_STATUS_OK(nRetVal); + + // init firmware + nRetVal = m_Firmware.Init((XnBool)m_ResetSensorOnStartup.GetValue(), (XnBool)m_LeanInit.GetValue()); + XN_IS_STATUS_OK(nRetVal); + m_bInitialized = TRUE; + + m_ResetSensorOnStartup.UpdateSetCallback(NULL, NULL); + m_LeanInit.UpdateSetCallback(NULL, NULL); + + // update device info properties + nRetVal = m_DeviceName.UnsafeUpdateValue(GetFixedParams()->GetDeviceName()); + XN_IS_STATUS_OK(nRetVal); + nRetVal = m_VendorSpecificData.UnsafeUpdateValue(GetFixedParams()->GetVendorData()); + XN_IS_STATUS_OK(nRetVal); + nRetVal = m_ID.UnsafeUpdateValue(GetFixedParams()->GetSensorSerial()); + XN_IS_STATUS_OK(nRetVal); + nRetVal = m_PlatformString.UnsafeUpdateValue(GetFixedParams()->GetPlatformString()); + XN_IS_STATUS_OK(nRetVal); + + // Add supported streams + AddSupportedStream(XN_STREAM_TYPE_DEPTH); + AddSupportedStream(XN_STREAM_TYPE_IR); + + if (GetFirmware()->GetInfo()->bImageSupported) + { + AddSupportedStream(XN_STREAM_TYPE_IMAGE); + } + + if (GetFirmware()->GetInfo()->bAudioSupported) + { + AddSupportedStream(XN_STREAM_TYPE_AUDIO); + } + + return XN_STATUS_OK; +} + +XnStatus XnSensor::Destroy() +{ + XnDevicePrivateData* pDevicePrivateData = GetDevicePrivateData(); + + if (m_hDisconnectedCallback != NULL) + { + XnDeviceEnumeration::DisconnectedEvent().Unregister(m_hDisconnectedCallback); + m_hDisconnectedCallback = NULL; + } + + // close Commands.txt thread + if (pDevicePrivateData->LogThread.hThread != NULL) + { + pDevicePrivateData->LogThread.bKillThread = TRUE; + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down Sensor commands.txt thread..."); + xnOSWaitAndTerminateThread(&pDevicePrivateData->LogThread.hThread, XN_DEVICE_SENSOR_THREAD_KILL_TIMEOUT); + pDevicePrivateData->LogThread.hThread = NULL; + } + + // if needed, close the streams + if (m_bInitialized && m_CloseStreamsOnShutdown.GetValue() == TRUE && + m_ReadData.GetValue() == TRUE && m_ErrorState.GetValue() != XN_STATUS_DEVICE_NOT_CONNECTED) + { + m_Firmware.GetParams()->m_Stream0Mode.SetValue(XN_VIDEO_STREAM_OFF); + m_Firmware.GetParams()->m_Stream1Mode.SetValue(XN_VIDEO_STREAM_OFF); + m_Firmware.GetParams()->m_Stream2Mode.SetValue(XN_AUDIO_STREAM_OFF); + } + + // close IO (including all reading threads) + m_SensorIO.CloseDevice(); + m_bInitialized = FALSE; + + // shutdown scheduler + if (m_pScheduler != NULL) + { + xnSchedulerShutdown(&m_pScheduler); + m_pScheduler = NULL; + } + + if (pDevicePrivateData->hEndPointsCS != NULL) + { + xnOSCloseCriticalSection(&pDevicePrivateData->hEndPointsCS); + pDevicePrivateData->hEndPointsCS = NULL; + } + + // free buffers + XnDeviceSensorFreeBuffers(pDevicePrivateData); + + if (pDevicePrivateData->hExecuteMutex != NULL) + { + xnOSCloseMutex(&pDevicePrivateData->hExecuteMutex); + pDevicePrivateData->hExecuteMutex = NULL; + } + + XnDeviceBase::Destroy(); + + // close dumps + xnDumpFileClose(pDevicePrivateData->TimestampsDump); + xnDumpFileClose(pDevicePrivateData->BandwidthDump); + xnDumpFileClose(pDevicePrivateData->MiniPacketsDump); + xnDumpFileClose(m_FrameSyncDump); + xnDumpFileClose(m_FirmwareLogDump); + + m_Firmware.Free(); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::CreateDeviceModule(XnDeviceModuleHolder** ppModuleHolder) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnDeviceBase::CreateDeviceModule(ppModuleHolder); + XN_IS_STATUS_OK(nRetVal); + + // add sensor properties + XnDeviceModule* pModule = (*ppModuleHolder)->GetModule(); + XnProperty* pProps[] = + { + &m_ErrorState, &m_ResetSensorOnStartup, &m_LeanInit, &m_Interface, &m_ReadData, &m_FirmwareParam, + &m_CmosBlankingUnits, &m_CmosBlankingTime, &m_Reset, &m_Version, + &m_FixedParam, &m_FrameSync, &m_FirmwareFrameSync, &m_CloseStreamsOnShutdown, &m_ID, + &m_VendorSpecificData, &m_AudioSupported, &m_ImageSupported, + &m_ImageControl, &m_DepthControl, &m_AHB, &m_LedState, &m_EmitterEnabled, &m_HostTimestamps, &m_PlatformString, + &m_FirmwareLogInterval, &m_FirmwareLogPrint, &m_FirmwareCPUInterval, &m_DeleteFile, + &m_APCEnabled, &m_TecSetPoint, &m_TecStatus, &m_TecFastConvergenceStatus, &m_EmitterSetPoint, &m_EmitterStatus, &m_I2C, + &m_FileAttributes, &m_FlashFile, &m_FirmwareLogFilter, &m_FirmwareLog, &m_FlashChunk, &m_FileList, + &m_ProjectorFault, &m_BIST, &m_FirmwareTecDebugPrint, &m_DeviceName + }; + + nRetVal = pModule->AddProperties(pProps, sizeof(pProps)/sizeof(XnProperty*)); + if (nRetVal != XN_STATUS_OK) + { + DestroyModule(*ppModuleHolder); + *ppModuleHolder = NULL; + return (nRetVal); + } + + // configure it from global file + if (m_strGlobalConfigFile[0] != '\0') + { + nRetVal = pModule->LoadConfigFromFile(m_strGlobalConfigFile); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::CreateStreamImpl(const XnChar* strType, const XnChar* strName, const XnActualPropertiesHash* pInitialSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnDeviceBase::CreateStreamImpl(strType, strName, pInitialSet); + XN_IS_STATUS_OK(nRetVal); + + // and configure it from global config file + nRetVal = ConfigureModuleFromGlobalFile(strName, strType); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::CreateStreamModule(const XnChar* StreamType, const XnChar* StreamName, XnDeviceModuleHolder** ppStreamHolder) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // make sure reading from streams is turned on + if (!m_ReadData.GetValue()) + { + nRetVal = m_ReadData.SetValue(TRUE); + XN_IS_STATUS_OK(nRetVal); + } + + XnDeviceStream* pStream; + XnSensorStreamHelper* pHelper; + + // create stream + if (strcmp(StreamType, XN_STREAM_TYPE_DEPTH) == 0) + { + XnSensorDepthStream* pDepthStream; + XN_VALIDATE_NEW(pDepthStream, XnSensorDepthStream, StreamName, &m_Objects); + pStream = pDepthStream; + pHelper = pDepthStream->GetHelper(); + } + else if (strcmp(StreamType, XN_STREAM_TYPE_IMAGE) == 0) + { + XnSensorImageStream* pImageStream; + XN_VALIDATE_NEW(pImageStream, XnSensorImageStream, StreamName, &m_Objects); + pStream = pImageStream; + pHelper = pImageStream->GetHelper(); + } + else if (strcmp(StreamType, XN_STREAM_TYPE_IR) == 0) + { + XnSensorIRStream* pIRStream; + XN_VALIDATE_NEW(pIRStream, XnSensorIRStream, StreamName, &m_Objects); + pStream = pIRStream; + pHelper = pIRStream->GetHelper(); + } + else if (strcmp(StreamType, XN_STREAM_TYPE_AUDIO) == 0) + { + // TODO: enable + XN_ASSERT(FALSE); + pStream = NULL; + pHelper = NULL; +/* if (!m_Firmware.GetInfo()->bAudioSupported) + { + XN_LOG_WARNING_RETURN(XN_STATUS_UNSUPPORTED_STREAM, XN_MASK_DEVICE_SENSOR, "Audio is not supported by this FW!"); + } + + // TODO: use the allow other users property when constructing the audio stream + XnSensorAudioStream* pAudioStream; + XN_VALIDATE_NEW(pAudioStream, XnSensorAudioStream, GetUSBPath(), StreamName, &m_Objects, FALSE); + pStream = pAudioStream; + pHelper = pAudioStream->GetHelper(); +*/ + } + else + { + XN_LOG_WARNING_RETURN(XN_STATUS_UNSUPPORTED_STREAM, XN_MASK_DEVICE_SENSOR, "Unsupported stream type: %s", StreamType); + } + + *ppStreamHolder = XN_NEW(XnSensorStreamHolder, pStream, pHelper); + + return (XN_STATUS_OK); +} + +void XnSensor::DestroyStreamModule(XnDeviceModuleHolder* pStreamHolder) +{ + XN_DELETE(pStreamHolder->GetModule()); + XN_DELETE(pStreamHolder); +} + +XnStatus XnSensor::OpenAllStreams() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Opening all streams..."); + + // take a list of all the streams + const XnChar* astrStreams[XN_SENSOR_MAX_STREAM_COUNT]; + XnUInt32 nStreamCount = XN_SENSOR_MAX_STREAM_COUNT; + XnDeviceStream* apStreams[XN_SENSOR_MAX_STREAM_COUNT]; + XnSensorStreamHolder* apSensorStreams[XN_SENSOR_MAX_STREAM_COUNT]; + + nRetVal = GetStreamNames(astrStreams, &nStreamCount); + XN_IS_STATUS_OK(nRetVal); + + for (XnUInt32 i = 0; i < nStreamCount; ++i) + { + XnDeviceModuleHolder* pHolder; + nRetVal = FindStream(astrStreams[i], &pHolder); + XN_IS_STATUS_OK(nRetVal); + + apSensorStreams[i] = (XnSensorStreamHolder*)(pHolder); + apStreams[i] = apSensorStreams[i]->GetStream(); + } + + // NOTE: the following is an ugly patch. When depth and IR both exist, Depth stream MUST be configured + // and opened BEFORE IR stream. So, generally, if one of the streams is depth, we move it to be first. + for (XnUInt32 i = 1; i < nStreamCount; ++i) + { + if (strcmp(apStreams[i]->GetType(), XN_STREAM_TYPE_DEPTH) == 0) + { + // switch it with the one in location 0 + const XnChar* strTempName = astrStreams[0]; + XnDeviceStream* pTempStream = apStreams[0]; + XnSensorStreamHolder* pTempHolder = apSensorStreams[0]; + + astrStreams[0] = astrStreams[i]; + apStreams[0] = apStreams[i]; + apSensorStreams[0] = apSensorStreams[i]; + + astrStreams[i] = strTempName; + apStreams[i] = pTempStream; + apSensorStreams[i] = pTempHolder; + break; + } + } + + // now configure them all + for (XnUInt32 i = 0; i < nStreamCount; ++i) + { + if (!apStreams[i]->IsOpen()) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Configuring stream %s...", apStreams[i]->GetName()); + nRetVal = apSensorStreams[i]->Configure(); + XN_IS_STATUS_OK(nRetVal); + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Stream %s is configured", apStreams[i]->GetName()); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Stream %s is already open.", apStreams[i]->GetName()); + } + } + + // and open them all + for (XnUInt32 i = 0; i < nStreamCount; ++i) + { + if (!apStreams[i]->IsOpen()) + { + nRetVal = apSensorStreams[i]->FinalOpen(); + XN_IS_STATUS_OK(nRetVal); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetStream(const XnChar* strStream, XnDeviceStream** ppStream) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModuleHolder* pHolder; + nRetVal = FindStream(strStream, &pHolder); + XN_IS_STATUS_OK(nRetVal); + + XnSensorStreamHolder* pSensorStreamHolder = (XnSensorStreamHolder*)(pHolder); + *ppStream = pSensorStreamHolder->GetStream(); + + return XN_STATUS_OK; +} + +XnStatus XnSensor::LoadConfigFromFile(const XnChar* csINIFilePath, const XnChar* csSectionName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XN_VALIDATE_INPUT_PTR(csINIFilePath); + XN_VALIDATE_INPUT_PTR(csSectionName); + + // we first need to configure the USB interface (we want to do so BEFORE creating streams) + nRetVal = m_Interface.ReadValueFromFile(csINIFilePath, XN_MODULE_NAME_DEVICE); + XN_IS_STATUS_OK(nRetVal); + + // now configure DEVICE module (primary stream, global mirror, etc.) + nRetVal = DeviceModule()->LoadConfigFromFile(csINIFilePath, XN_MODULE_NAME_DEVICE); + XN_IS_STATUS_OK(nRetVal); + + // and now configure the streams + XnDeviceModuleHolderList streams; + nRetVal = GetStreamsList(streams); + XN_IS_STATUS_OK(nRetVal); + + for (XnDeviceModuleHolderList::Iterator it = streams.Begin(); it != streams.End(); ++it) + { + XnDeviceModuleHolder* pHolder = *it; + nRetVal = pHolder->GetModule()->LoadConfigFromFile(csINIFilePath); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::InitReading() +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnSensorUsbInterface prevInterface = GetCurrentUsbInterface(); + + // open data endpoints + nRetVal = m_SensorIO.OpenDataEndPoints((XnSensorUsbInterface)m_Interface.GetValue(), *m_Firmware.GetInfo()); + XN_IS_STATUS_OK(nRetVal); + + XnSensorUsbInterface currInterface = GetCurrentUsbInterface(); + nRetVal = m_Interface.UnsafeUpdateValue(currInterface); + XN_IS_STATUS_OK(nRetVal); + + if (prevInterface != currInterface) + { + nRetVal = XnHostProtocolUpdateSupportedImageModes(&m_DevicePrivateData); + XN_IS_STATUS_OK(nRetVal); + } + + // take frequency information + XnFrequencyInformation FrequencyInformation; + + nRetVal = XnHostProtocolAlgorithmParams(&m_DevicePrivateData, XN_HOST_PROTOCOL_ALGORITHM_FREQUENCY, &FrequencyInformation, sizeof(XnFrequencyInformation), (XnResolutions)0, 0); + if (nRetVal != XN_STATUS_OK) + return nRetVal; + + m_DevicePrivateData.fDeviceFrequency = XN_PREPARE_VAR_FLOAT_IN_BUFFER(FrequencyInformation.fDeviceFrequency); + + // Init Dumps + m_DevicePrivateData.BandwidthDump = xnDumpFileOpen(XN_DUMP_BANDWIDTH, "Bandwidth.csv"); + xnDumpFileWriteString(m_DevicePrivateData.BandwidthDump, "Timestamp,Frame Type,Frame ID,Size\n"); + m_DevicePrivateData.TimestampsDump = xnDumpFileOpen(XN_DUMP_TIMESTAMPS, "Timestamps.csv"); + xnDumpFileWriteString(m_DevicePrivateData.TimestampsDump, "Host Time (us),Stream,Device TS,Time (ms),Comments\n"); + m_DevicePrivateData.MiniPacketsDump = xnDumpFileOpen(XN_DUMP_MINI_PACKETS, "MiniPackets.csv"); + xnDumpFileWriteString(m_DevicePrivateData.MiniPacketsDump, "HostTS,Type,ID,Size,Timestamp\n"); + + m_DevicePrivateData.nGlobalReferenceTS = 0; + nRetVal = xnOSCreateCriticalSection(&m_DevicePrivateData.hEndPointsCS); + XN_IS_STATUS_OK(nRetVal); + + // NOTE: when we go up, some streams might be open, and so we'll receive lots of garbage. + // wait till streams are turned off, and then start reading. +// pDevicePrivateData->bIgnoreDataPackets = TRUE; + + // open input threads + nRetVal = XnDeviceSensorOpenInputThreads(GetDevicePrivateData()); + XN_IS_STATUS_OK(nRetVal); + + // open 'commands.txt' thread + nRetVal = xnOSCreateThread(XnDeviceSensorProtocolScriptThread, (XN_THREAD_PARAM)&m_DevicePrivateData, &m_DevicePrivateData.LogThread.hThread); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnSensor::ChangeTaskInterval(XnScheduledTask** ppTask, XnTaskCallbackFuncPtr pCallback, XnUInt32 nInterval) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (*ppTask == NULL) + { + nRetVal = xnSchedulerAddTask(m_pScheduler, nInterval, pCallback, this, ppTask); + XN_IS_STATUS_OK(nRetVal); + } + else // already scheduled + { + if (nInterval == 0) + { + nRetVal = xnSchedulerRemoveTask(m_pScheduler, ppTask); + XN_IS_STATUS_OK(nRetVal); + + *ppTask = NULL; + } + else + { + nRetVal = xnSchedulerRescheduleTask(m_pScheduler, *ppTask, nInterval); + XN_IS_STATUS_OK(nRetVal); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::ValidateSensorID(XnChar* csSensorID) +{ + if (strcmp(csSensorID, XN_DEVICE_SENSOR_DEFAULT_ID) != 0) + { + if (strcmp(csSensorID, GetFixedParams()->GetSensorSerial()) != 0) + { + return (XN_STATUS_IO_DEVICE_WRONG_SERIAL); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::ResolveGlobalConfigFileName(XnChar* strConfigFile, XnUInt32 nBufSize, const XnChar* strConfigDir) +{ + // If strConfigDir is NULL, tries to resolve the config file based on the driver's directory + XnChar strBaseDir[XN_FILE_MAX_PATH]; + if (strConfigDir == NULL) + { + if (xnOSGetModulePathForProcAddress(reinterpret_cast(&XnSensor::ResolveGlobalConfigFileName), strBaseDir) == XN_STATUS_OK && + xnOSGetDirName(strBaseDir, strBaseDir, XN_FILE_MAX_PATH) == XN_STATUS_OK) + { + // Successfully obtained the driver's path + strConfigDir = strBaseDir; + } + else + { + // Something wrong happened. Use the current directory as the fallback. + strConfigDir = "."; + } + } + + XnStatus rc; + XN_VALIDATE_STR_COPY(strConfigFile, strConfigDir, nBufSize, rc); + return xnOSAppendFilePath(strConfigFile, XN_GLOBAL_CONFIG_FILE_NAME, nBufSize); +} + +XnStatus XnSensor::SetGlobalConfigFile(const XnChar* strConfigFile) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSStrCopy(m_strGlobalConfigFile, strConfigFile, XN_FILE_MAX_PATH); + XN_IS_STATUS_OK(nRetVal); + + XnBool bExists; + nRetVal = xnOSDoesFileExist(m_strGlobalConfigFile, &bExists); + XN_IS_STATUS_OK(nRetVal); + + if (!bExists) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Global configuration file '%s' was not found.", m_strGlobalConfigFile); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::ConfigureModuleFromGlobalFile(const XnChar* strModule, const XnChar* strSection /* = NULL */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDeviceModule* pModule; + nRetVal = FindModule(strModule, &pModule); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pModule->LoadConfigFromFile(m_strGlobalConfigFile, strSection); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetFirmwareParam(XnInnerParamData* pParam) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolGetParam(&m_DevicePrivateData, pParam->nParam, pParam->nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::ReadAHB(XnAHBData* pAHB) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolReadAHB(&m_DevicePrivateData, pAHB->nRegister, pAHB->nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::WriteAHB(const XnAHBData* pAHB) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolWriteAHB(&m_DevicePrivateData, pAHB->nRegister, pAHB->nValue, pAHB->nMask); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetLedState(XnUInt16 nLedId, XnUInt16 nState) +{ + return XnHostProtocolSetLedState(&m_DevicePrivateData, nLedId, nState); +} + +XnStatus XnSensor::SetEmitterState(XnBool bActive) +{ + return XnHostProtocolSetEmitterState(&m_DevicePrivateData, bActive); +} + +XnStatus XnSensor::SetFirmwareFrameSync(XnBool bOn) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmware()->GetParams()->m_FrameSyncEnabled.SetValue(bOn); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_FirmwareFrameSync.UnsafeUpdateValue(bOn); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnSensor::ReadFirmwareLog() +{ + // get log + XnChar LogBuffer[XN_MAX_LOG_SIZE] = ""; + XnHostProtocolGetLog(&m_DevicePrivateData, LogBuffer, XN_MAX_LOG_SIZE); + + // write sensor log to dump + xnDumpFileWriteString(m_FirmwareLogDump, LogBuffer); + + // print + if (m_FirmwareLogPrint.GetValue()) + { + printf("%s", LogBuffer); + } +} + +void XnSensor::ReadFirmwareCPU() +{ + XnTaskCPUInfo aTasks[100]; + XnUInt32 nTasksCount = 100; + XnStatus nRetVal = XnHostProtocolGetCPUStats(&m_DevicePrivateData, aTasks, &nTasksCount); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "GetCPUStats failed execution: %s", xnGetStatusString(nRetVal)); + return; + } + + // sum it all up + XnUInt64 nSum = 0; + for (XnUInt32 nIndex = 0; nIndex < nTasksCount; ++nIndex) + nSum += aTasks[nIndex].nTimeInMicroSeconds; + + // print + printf("Task ID Total Time (us) Percentage Times Avg. Time Per Call\n"); + printf("======= =============== ========== ======= ==================\n"); + for (XnUInt32 nIndex = 0; nIndex < nTasksCount; ++nIndex) + { + printf("%7u %15u %10.3f %7u %18.3f\n", + nIndex, aTasks[nIndex].nTimeInMicroSeconds, + aTasks[nIndex].nTimeInMicroSeconds * 100.0 / nSum, + aTasks[nIndex].nTimesExecuted, + (double)aTasks[nIndex].nTimeInMicroSeconds / (double)aTasks[nIndex].nTimesExecuted); + } +} + +XnStatus XnSensor::GetI2C(XnI2CReadData* pI2CReadData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolReadI2C(&m_DevicePrivateData, pI2CReadData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetTecStatus(XnTecData* pTecData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolGetTecData(&m_DevicePrivateData, pTecData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetTecFastConvergenceStatus(XnTecFastConvergenceData* pTecData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolGetTecFastConvergenceData(&m_DevicePrivateData, pTecData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetEmitterStatus(XnEmitterData* pEmitterData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolGetEmitterData(&m_DevicePrivateData, pEmitterData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::ReadFlashFile(const XnParamFileData* pFile) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolFileDownload(&m_DevicePrivateData, (XnUInt16)pFile->nOffset, pFile->strFileName); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::ReadFlashChunk(XnParamFlashData* pFlash) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolReadFlash(&m_DevicePrivateData, pFlash->nOffset, pFlash->nSize, pFlash->pData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetCmosBlankingUnits(XnCmosBlankingUnits* pBlanking) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer < XN_SENSOR_FW_VER_5_1) + { + return (XN_STATUS_IO_DEVICE_FUNCTION_NOT_SUPPORTED); + } + + nRetVal = XnHostProtocolGetCmosBlanking(&m_DevicePrivateData, pBlanking->nCmosID, &pBlanking->nUnits); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetCmosBlankingTime(XnCmosBlankingTime* pBlanking) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // check version + if (m_Firmware.GetInfo()->nFWVer < XN_SENSOR_FW_VER_5_1) + { + return (XN_STATUS_IO_DEVICE_FUNCTION_NOT_SUPPORTED); + } + + // get value in units + XnCmosBlankingUnits blankingUnits; + blankingUnits.nCmosID = pBlanking->nCmosID; + nRetVal = GetCmosBlankingUnits(&blankingUnits); + XN_IS_STATUS_OK(nRetVal); + + // get coefficients + const XnCmosBlankingCoefficients* pCoeffs = m_CmosInfo.GetBlankingCoefficients(pBlanking->nCmosID); + + // translate to time + pBlanking->nTimeInMilliseconds = (pCoeffs->fA * blankingUnits.nUnits + pCoeffs->fB)/1000; + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetFirmwareMode(XnParamCurrentMode* pnMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer == XN_SENSOR_FW_VER_0_17) + { + *pnMode = m_Firmware.GetInfo()->nCurrMode; + } + else + { + XnUInt16 nMode; + nRetVal = XnHostProtocolGetMode(&m_DevicePrivateData, nMode); + XN_IS_STATUS_OK(nRetVal); + + switch (nMode) + { + case XN_HOST_PROTOCOL_MODE_PS: + *pnMode = XN_MODE_PS; + break; + case XN_HOST_PROTOCOL_MODE_MAINTENANCE: + *pnMode = XN_MODE_MAINTENANCE; + break; + case XN_HOST_PROTOCOL_MODE_SAFE_MODE: + *pnMode = XN_MODE_SAFE_MODE; + break; + default: + printf("Got Unknown Firmware Mode %d\n", nMode); + return XN_STATUS_DEVICE_BAD_PARAM; + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetDepthCmosRegister(XnControlProcessingData* pRegister) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer >= XN_SENSOR_FW_VER_3_0) + { + nRetVal = XnHostProtocolGetCMOSRegisterI2C(&m_DevicePrivateData, XN_CMOS_TYPE_DEPTH, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = XnHostProtocolGetCMOSRegister(&m_DevicePrivateData, XN_CMOS_TYPE_DEPTH, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetImageCmosRegister(XnControlProcessingData* pRegister) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer >= XN_SENSOR_FW_VER_3_0) + { + nRetVal = XnHostProtocolGetCMOSRegisterI2C(&m_DevicePrivateData, XN_CMOS_TYPE_IMAGE, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = XnHostProtocolGetCMOSRegister(&m_DevicePrivateData, XN_CMOS_TYPE_IMAGE, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetFirmwareLog(XnChar* csLog, XnUInt32 nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolGetLog(&m_DevicePrivateData, csLog, nSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetFileList(XnFlashFileList* pFileList) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolGetFileList(&m_DevicePrivateData, 0, pFileList->pFiles, pFileList->nFiles); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::GetFixedParams(XnDynamicSizeBuffer* pBuffer) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (pBuffer->nMaxSize < sizeof(XnFixedParams)) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + XnFixedParams fixed; + nRetVal = XnHostProtocolGetFixedParams(GetDevicePrivateData(), fixed); + XN_IS_STATUS_OK(nRetVal); + + xnOSMemCopy(pBuffer->pData, &fixed, sizeof(XnFixedParams)); + pBuffer->nDataSize = sizeof(XnFixedParams); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::RunBIST(XnUInt32 nTestsMask, XnUInt32* pnFailures) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // always perform soft reset before running bist + nRetVal = XnHostProtocolReset(&m_DevicePrivateData, XN_RESET_TYPE_SOFT); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolRunBIST(&m_DevicePrivateData, nTestsMask, pnFailures); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetErrorState(XnStatus errorState) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (errorState != GetErrorState()) + { + if (errorState == XN_STATUS_OK) + { + xnLogInfo(XN_MASK_DEVICE_SENSOR, "Device is back to normal state."); + } + else + { + xnLogError(XN_MASK_DEVICE_SENSOR, "Device has entered error mode: %s", xnGetStatusString(errorState)); + } + + nRetVal = m_ErrorState.UnsafeUpdateValue((XnUInt64)errorState); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetInterface(XnSensorUsbInterface nInterface) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // we don't allow change if requested value is specific and different than current + if (m_ReadData.GetValue() == TRUE && + nInterface != XN_SENSOR_USB_INTERFACE_DEFAULT && + nInterface != GetCurrentUsbInterface()) + { + return (XN_STATUS_DEVICE_PROPERTY_READ_ONLY); + } + + nRetVal = m_Interface.UnsafeUpdateValue(nInterface); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetHostTimestamps(XnBool bHostTimestamps) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // we don't allow change if requested value is specific and different than current + if (m_ReadData.GetValue() == TRUE && + bHostTimestamps != (XnBool)m_HostTimestamps.GetValue()) + { + return (XN_STATUS_DEVICE_PROPERTY_READ_ONLY); + } + + nRetVal = m_HostTimestamps.UnsafeUpdateValue(bHostTimestamps); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetReadData(XnBool bRead) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!bRead) + { + return XN_STATUS_ERROR; + } + else + { + nRetVal = InitReading(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_ReadData.UnsafeUpdateValue(TRUE); + XN_IS_STATUS_OK(nRetVal); + + // no longer needed + m_ReadData.UpdateSetCallback(NULL, NULL); + + XnHostProtocolUpdateSupportedImageModes(&m_DevicePrivateData); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetDepthCmosRegister(const XnControlProcessingData* pRegister) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer >= XN_SENSOR_FW_VER_3_0) + { + nRetVal = XnHostProtocolSetCMOSRegisterI2C(&m_DevicePrivateData, XN_CMOS_TYPE_DEPTH, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = XnHostProtocolSetCMOSRegister(&m_DevicePrivateData, XN_CMOS_TYPE_DEPTH, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetImageCmosRegister(const XnControlProcessingData* pRegister) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer >= XN_SENSOR_FW_VER_3_0) + { + nRetVal = XnHostProtocolSetCMOSRegisterI2C(&m_DevicePrivateData, XN_CMOS_TYPE_IMAGE, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = XnHostProtocolSetCMOSRegister(&m_DevicePrivateData, XN_CMOS_TYPE_IMAGE, pRegister->nRegister, pRegister->nValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetFirmwareLogFilter(XnUInt32 nFilter) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // set the firmware param (this prop will be updated accordingly) + nRetVal = m_Firmware.GetParams()->m_LogFilter.SetValue(nFilter); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetFirmwareLogInterval(XnUInt32 nMilliSeconds) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = ChangeTaskInterval(&m_pLogTask, ExecuteFirmwareLogTask, nMilliSeconds); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_FirmwareLogInterval.UnsafeUpdateValue(nMilliSeconds); + XN_IS_STATUS_OK(nRetVal); + + if (nMilliSeconds == 0) + { + xnDumpFileClose(m_FirmwareLogDump); + } + else + { + m_FirmwareLogDump = xnDumpFileOpenEx("FirmwareLog", TRUE, TRUE, "Sensor.log"); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetFirmwareLogPrint(XnBool bPrint) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_FirmwareLogPrint.UnsafeUpdateValue(bPrint); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetFirmwareCPUInterval(XnUInt32 nMilliSeconds) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = ChangeTaskInterval(&m_pCPUTask, ExecuteFirmwareCPUTask, nMilliSeconds); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_FirmwareCPUInterval.UnsafeUpdateValue(nMilliSeconds); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetAPCEnabled(XnBool bEnabled) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // set firmware param (we will be updated via synch mechanism) + nRetVal = m_Firmware.GetParams()->m_APCEnabled.SetValue(bEnabled); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetI2C(const XnI2CWriteData* pI2CWriteData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolWriteI2C(&m_DevicePrivateData, pI2CWriteData); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::DeleteFile(XnUInt16 nFileID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolDeleteFile(&m_DevicePrivateData, nFileID); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetTecSetPoint(XnUInt16 nSetPoint) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolCalibrateTec(&m_DevicePrivateData, nSetPoint); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetEmitterSetPoint(XnUInt16 nSetPoint) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolCalibrateEmitter(&m_DevicePrivateData, nSetPoint); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetFileAttributes(const XnFileAttributes* pAttributes) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolSetFileAttributes(&m_DevicePrivateData, pAttributes->nId, pAttributes->nAttribs); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetFirmwareParam(const XnInnerParamData* pParam) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolSetParam(&m_DevicePrivateData, pParam->nParam, pParam->nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::WriteFlashFile(const XnParamFileData* pFile) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogInfo(XN_MASK_SENSOR_PROTOCOL, "Upload file %s (offset %d)", pFile->strFileName, pFile->nOffset); + + nRetVal = XnHostProtocolFileUpload(&m_DevicePrivateData, pFile->nOffset, pFile->strFileName, pFile->nAttributes); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetCmosBlankingUnits(const XnCmosBlankingUnits* pBlanking) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer < XN_SENSOR_FW_VER_5_1) + { + return (XN_STATUS_IO_DEVICE_FUNCTION_NOT_SUPPORTED); + } + + nRetVal = XnHostProtocolSetCmosBlanking(&m_DevicePrivateData, pBlanking->nUnits, pBlanking->nCmosID, pBlanking->nNumberOfFrames); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetCmosBlankingTime(const XnCmosBlankingTime* pBlanking) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // check version + if (m_Firmware.GetInfo()->nFWVer < XN_SENSOR_FW_VER_5_1) + { + return (XN_STATUS_IO_DEVICE_FUNCTION_NOT_SUPPORTED); + } + + // get coefficients + const XnCmosBlankingCoefficients* pCoeffs = m_CmosInfo.GetBlankingCoefficients(pBlanking->nCmosID); + + // translate to units request + XnCmosBlankingUnits blankingUnits; + blankingUnits.nCmosID = pBlanking->nCmosID; + blankingUnits.nNumberOfFrames = pBlanking->nNumberOfFrames; + blankingUnits.nUnits = XnUInt16((pBlanking->nTimeInMilliseconds*1000 - pCoeffs->fB)/pCoeffs->fA); + + nRetVal = SetCmosBlankingUnits(&blankingUnits); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::Reset(XnParamResetType nType) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolReset(&m_DevicePrivateData, (XnUInt16)nType); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetFirmwareMode(XnParamCurrentMode nMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Firmware.GetInfo()->nFWVer == XN_SENSOR_FW_VER_0_17) + { + m_Firmware.GetInfo()->nCurrMode = nMode; + return (XN_STATUS_OK); + } + + XnHostProtocolModeType nActualValue; + + switch (nMode) + { + case XN_MODE_PS: + nActualValue = XN_HOST_PROTOCOL_MODE_PS; + break; + case XN_MODE_MAINTENANCE: + nActualValue = XN_HOST_PROTOCOL_MODE_MAINTENANCE; + break; + default: + return XN_STATUS_DEVICE_UNSUPPORTED_MODE; + } + + nRetVal = XnHostProtocolSetMode(&m_DevicePrivateData, (XnUInt16)nActualValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensor::SetProjectorFault(XnProjectorFaultData* pProjectorFaultData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnHostProtocolCalibrateProjectorFault(&m_DevicePrivateData, pProjectorFaultData->nMinThreshold, pProjectorFaultData->nMaxThreshold, &pProjectorFaultData->bProjectorFaultEvent); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnSensor::ExecuteFirmwareLogTask(void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + pThis->ReadFirmwareLog(); +} + +void XnSensor::ExecuteFirmwareCPUTask(void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + pThis->ReadFirmwareCPU(); +} + +XnStatus XnSensor::OnFrameSyncPropertyChanged() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_ReadData.GetValue() == TRUE) + { + // decide firmware frame sync - both streams are on, and user asked for it + XnBool bFrameSync = ( + m_FrameSync.GetValue() == TRUE && + GetFirmware()->GetParams()->m_Stream0Mode.GetValue() == XN_VIDEO_STREAM_COLOR && + GetFirmware()->GetParams()->m_Stream1Mode.GetValue() == XN_VIDEO_STREAM_DEPTH + ); + + nRetVal = SetFirmwareFrameSync(bFrameSync); + XN_IS_STATUS_OK(nRetVal); + + // Set frame sync enabled flag (so mechanism will not be activated in case a stream is turned off). + m_frameSyncCs.Lock(); + m_nFrameSyncEnabled = bFrameSync; + m_frameSyncCs.Unlock(); + } + + return (XN_STATUS_OK); +} + +void XnSensor::OnNewStreamData(XnDeviceStream* pStream, OniFrame* pFrame) +{ + // Lock critical section. + m_frameSyncCs.Lock(); + + // Find the relevant stream in the frame-synced streams. + FrameSyncedStream* pFrameSyncedStream = NULL; + XnUInt32 nValidFrameCount = 0; // received frame + XnUInt32 nFrameSyncStreamCount = m_FrameSyncedStreams.GetSize(); + int receivedFrameId = pFrame->frameIndex; + for (XnUInt32 i = 0; m_nFrameSyncEnabled && (i < nFrameSyncStreamCount); ++i) + { + if (pStream == m_FrameSyncedStreams[i].pStream) + { + // Verify frame is valid. + if (pFrame != NULL) + { + // Release old frame and assign new frame. + if (m_FrameSyncedStreams[i].pFrame != NULL) + { + m_FrameSyncedStreams[i].pStream->ReleaseFrame(m_FrameSyncedStreams[i].pFrame); + } + + // Store the frame, timestamp and frame ID. + m_FrameSyncedStreams[i].pFrame = pFrame; + pStream->AddRefToFrame(pFrame); + pFrameSyncedStream = &m_FrameSyncedStreams[i]; + nValidFrameCount++; + } + } + else if (m_FrameSyncedStreams[i].pFrame != NULL) + { + // Check if there is a stored frame which has older timestamp than allowed. + XnUInt64 diff = (pFrame->timestamp > m_FrameSyncedStreams[i].pFrame->timestamp) ? + pFrame->timestamp - m_FrameSyncedStreams[i].pFrame->timestamp : + m_FrameSyncedStreams[i].pFrame->timestamp - pFrame->timestamp; + if (diff > FRAME_SYNC_MAX_FRAME_TIME_DIFF) + { + // Check if received frame has newer timestamp than stored one. + if (pFrame->timestamp > m_FrameSyncedStreams[i].pFrame->timestamp) + { + // Release the last frame. + m_FrameSyncedStreams[i].pStream->ReleaseFrame(m_FrameSyncedStreams[i].pFrame); + m_FrameSyncedStreams[i].pFrame = NULL; + } + // Newest frame should be released. + else + { + // Check whether frame was updated in the relevant frame synced stream. + if (pFrameSyncedStream != NULL) + { + // Release the stored new frame. + pFrameSyncedStream->pStream->ReleaseFrame(pFrameSyncedStream->pFrame); + pFrameSyncedStream->pFrame = NULL; + nValidFrameCount--; + } + else + { + // Release the new frame. + pFrame = NULL; + } + } + break; + } + else + { + ++nValidFrameCount; + } + } + } + + // Check whether stream is frame synced. + if (m_nFrameSyncEnabled && (pFrameSyncedStream != NULL)) + { + // Check if all the frames arrived. + if (nValidFrameCount == nFrameSyncStreamCount) + { + // Send all the frames. + ++m_nFrameSyncLastFrameID; + for (XnUInt32 i = 0; i < nFrameSyncStreamCount; ++i) + { + // Send the frame. + m_FrameSyncedStreams[i].pFrame->frameIndex = m_nFrameSyncLastFrameID; + XnDeviceBase::OnNewStreamData(m_FrameSyncedStreams[i].pStream, m_FrameSyncedStreams[i].pFrame); + m_FrameSyncedStreams[i].pStream->ReleaseFrame(m_FrameSyncedStreams[i].pFrame); + m_FrameSyncedStreams[i].pFrame = NULL; + } + } + + // Unlock critical section. + m_frameSyncCs.Unlock(); + } + else + { + // Unlock critical section. + m_frameSyncCs.Unlock(); + + // Set frame ID to higher of self and frame ID, to make sure IDs are incremental. + m_nFrameSyncLastFrameID = (m_nFrameSyncLastFrameID < receivedFrameId) ? receivedFrameId : m_nFrameSyncLastFrameID; + + if (pFrame != NULL) + { + // Send the frame (it is not frame-synced). + XnDeviceBase::OnNewStreamData(pStream, pFrame); + } + } +} + +XnStatus XnSensor::SetFrameSyncStreamGroup(XnDeviceStream** ppStreamList, XnUInt32 numStreams) +{ + // Lock critical section. + m_frameSyncCs.Lock(); + + // Set the frame sync property in the device. + XnStatus rc = SetProperty(XN_MODULE_NAME_DEVICE, XN_MODULE_PROPERTY_FRAME_SYNC, + (XnUInt64)((numStreams > 0) ? TRUE : FALSE)); + if (rc != XN_STATUS_OK) + { + // Unlock critical section. + m_frameSyncCs.Unlock(); + return rc; + } + + // Clear all the streams from frame-sync list. + XnUInt32 nFrameSyncStreamCount = m_FrameSyncedStreams.GetSize(); + for (XnUInt32 i = 0; i < nFrameSyncStreamCount; ++i) + { + // Release stored frame. + if (m_FrameSyncedStreams[i].pFrame != NULL) + { + // Release the frame. + m_FrameSyncedStreams[i].pStream->ReleaseFrame(m_FrameSyncedStreams[i].pFrame); + m_FrameSyncedStreams[i].pFrame = NULL; + } + + // Clear pointer to stream, timestamp and frame ID. + m_FrameSyncedStreams[i].pStream = NULL; + } + + // Check if creating group. + if (numStreams > 0) + { + m_FrameSyncedStreams.SetSize(numStreams); + + // Add streams to frame-sync list. + for (XnUInt32 i = 0; i < numStreams; ++i) + { + m_FrameSyncedStreams[i].pStream = ppStreamList[i]; + m_FrameSyncedStreams[i].pFrame = NULL; + } + } + else + { + // Zero the size of the frame sync group. + m_FrameSyncedStreams.SetSize(0); + } + + // Unlock critical section. + m_frameSyncCs.Unlock(); + + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetInterfaceCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->XnSensor::SetInterface((XnSensorUsbInterface)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetHostTimestampsCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->XnSensor::SetHostTimestamps(nValue == 1); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetReadDataCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->XnSensor::SetReadData((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFirmwareParamCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnInnerParamData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFirmwareParam((const XnInnerParamData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetCmosBlankingUnitsCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnCmosBlankingUnits); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetCmosBlankingUnits((const XnCmosBlankingUnits*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetCmosBlankingTimeCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnCmosBlankingTime); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetCmosBlankingTime((const XnCmosBlankingTime*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::ResetCallback(XnIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->Reset((XnParamResetType)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFirmwareModeCallback(XnIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFirmwareMode((XnParamCurrentMode)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetFirmwareParamCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnInnerParamData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetFirmwareParam((XnInnerParamData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetCmosBlankingUnitsCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnCmosBlankingUnits); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetCmosBlankingUnits((XnCmosBlankingUnits*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetCmosBlankingTimeCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnCmosBlankingTime); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetCmosBlankingTime((XnCmosBlankingTime*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetFirmwareModeCallback(const XnIntProperty* /*pSender*/, XnUInt64* pnValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + XnParamCurrentMode nMode; + XnStatus nRetVal = pThis->GetFirmwareMode(&nMode); + XN_IS_STATUS_OK(nRetVal); + + *pnValue = nMode; + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetAudioSupportedCallback(const XnIntProperty* /*pSender*/, XnUInt64* pnValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + *pnValue = pThis->m_Firmware.GetInfo()->bAudioSupported; + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetImageSupportedCallback(const XnIntProperty* /*pSender*/, XnUInt64* pnValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + *pnValue = pThis->m_Firmware.GetInfo()->bImageSupported; + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensor::FrameSyncPropertyChangedCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->OnFrameSyncPropertyChanged(); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetFixedParamsCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnDynamicSizeBuffer); + XnSensor* pThis = (XnSensor*)pCookie; + XnDynamicSizeBuffer* pBuffer = (XnDynamicSizeBuffer*)gbValue.data; + return pThis->GetFixedParams(pBuffer); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetInstanceCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + if (gbValue.dataSize != sizeof(void*)) + { + return XN_STATUS_DEVICE_PROPERTY_SIZE_DONT_MATCH; + } + + *(void**)gbValue.data = pCookie; + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetDepthCmosRegisterCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnControlProcessingData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetDepthCmosRegister((const XnControlProcessingData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetImageCmosRegisterCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnControlProcessingData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetImageCmosRegister((const XnControlProcessingData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetDepthCmosRegisterCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnControlProcessingData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetDepthCmosRegister((XnControlProcessingData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetImageCmosRegisterCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnControlProcessingData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetImageCmosRegister((XnControlProcessingData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::WriteAHBCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnAHBData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->WriteAHB((const XnAHBData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::ReadAHBCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnAHBData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->ReadAHB((XnAHBData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetLedStateCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnLedState); + XnSensor* pThis = (XnSensor*)pCookie; + const XnLedState* pLedState = (const XnLedState*)gbValue.data; + return pThis->SetLedState(pLedState->nLedID, pLedState->nState); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetEmitterStateCallback(XnIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetEmitterState(nValue == TRUE); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFirmwareFrameSyncCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFirmwareFrameSync(nValue == TRUE); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFirmwareLogFilterCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFirmwareLogFilter((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFirmwareLogIntervalCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFirmwareLogInterval((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFirmwareLogPrintCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFirmwareLogPrint((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFirmwareCPUIntervalCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFirmwareCPUInterval((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetAPCEnabledCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetAPCEnabled((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetI2CCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnI2CWriteData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetI2C((const XnI2CWriteData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::DeleteFileCallback(XnIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->DeleteFile((XnUInt16)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetTecSetPointCallback(XnIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetTecSetPoint((XnUInt16)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetEmitterSetPointCallback(XnIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetEmitterSetPoint((XnUInt16)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetFileAttributesCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnFileAttributes); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetFileAttributes((const XnFileAttributes*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::WriteFlashFileCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnParamFileData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->WriteFlashFile((const XnParamFileData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::SetProjectorFaultCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnProjectorFaultData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->SetProjectorFault((XnProjectorFaultData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetI2CCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnI2CReadData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetI2C((XnI2CReadData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetTecStatusCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnTecData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetTecStatus((XnTecData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetTecFastConvergenceStatusCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnTecFastConvergenceData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetTecFastConvergenceStatus((XnTecFastConvergenceData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetEmitterStatusCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnEmitterData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetEmitterStatus((XnEmitterData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::ReadFlashFileCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnParamFileData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->ReadFlashFile((const XnParamFileData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetFirmwareLogCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetFirmwareLog((XnChar*)gbValue.data, gbValue.dataSize); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::ReadFlashChunkCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnParamFlashData); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->ReadFlashChunk((XnParamFlashData*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::GetFileListCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnFlashFileList); + XnSensor* pThis = (XnSensor*)pCookie; + return pThis->GetFileList((XnFlashFileList*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensor::RunBISTCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XN_VALIDATE_GENERAL_BUFFER_TYPE(gbValue, XnBist); + XnSensor* pThis = (XnSensor*)pCookie; + XnBist* pBist = (XnBist*)gbValue.data; + XnStatus nRetVal = pThis->RunBIST(pBist->nTestsMask, &pBist->nFailures); + XN_IS_STATUS_OK(nRetVal); + return XN_STATUS_OK; +} + +void XN_CALLBACK_TYPE XnSensor::OnDeviceDisconnected(const OniDeviceInfo& deviceInfo, void* pCookie) +{ + XnSensor* pThis = (XnSensor*)pCookie; + if (xnOSStrCmp(deviceInfo.uri, pThis->GetUSBPath()) == 0) + { + pThis->SetErrorState(XN_STATUS_DEVICE_NOT_CONNECTED); + } +} + diff --git a/Source/Drivers/PS1080/Sensor/XnSensor.h b/Source/Drivers/PS1080/Sensor/XnSensor.h new file mode 100644 index 0000000..40b5285 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensor.h @@ -0,0 +1,307 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_H__ +#define __XN_SENSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnDeviceSensorIO.h" +#include "XnParams.h" +#include "XnDeviceSensor.h" +#include "XnSensorFixedParams.h" +#include "XnSensorFirmwareParams.h" +#include +#include "XnSensorFirmware.h" +#include "XnCmosInfo.h" +#include "IXnSensorStream.h" +#include +#include "XnArray.h" + +//--------------------------------------------------------------------------- +// XnSensor class +//--------------------------------------------------------------------------- +class XnSensor : public XnDeviceBase +{ + friend class XnServerSensorInvoker; + +public: + XnSensor(XnBool bResetOnStartup = TRUE, XnBool bLeanInit = FALSE); + ~XnSensor(); + + virtual XnStatus InitImpl(const XnDeviceConfig* pDeviceConfig); + virtual XnStatus Destroy(); + virtual XnStatus OpenAllStreams(); + virtual XnStatus LoadConfigFromFile(const XnChar* csINIFilePath, const XnChar* csSectionName); + +public: + inline XnSensorFixedParams* GetFixedParams() { return GetFirmware()->GetFixedParams(); } + inline XnSensorFirmware* GetFirmware() { return &m_Firmware; } + inline XnSensorFPS* GetFPSCalculator() { return &m_FPS; } + + XnStatus SetCmosConfiguration(XnCMOSType nCmos, XnResolutions nRes, XnUInt32 nFPS); + + inline XnDevicePrivateData* GetDevicePrivateData() { return &m_DevicePrivateData; } + + XnStatus ConfigPropertyFromFile(XnStringProperty* pProperty, const XnChar* csINIFilePath, const XnChar* csSectionName); + XnStatus ConfigPropertyFromFile(XnIntProperty* pProperty, const XnChar* csINIFilePath, const XnChar* csSectionName); + + inline XnBool IsMiscSupported() const { return m_SensorIO.IsMiscEndpointSupported(); } + inline XnBool IsLowBandwidth() const { return m_SensorIO.IsLowBandwidth(); } + inline XnSensorUsbInterface GetCurrentUsbInterface() const { return m_SensorIO.GetCurrentInterface(*m_Firmware.GetInfo()); } + + XnStatus GetStream(const XnChar* strStream, XnDeviceStream** ppStream); + + inline XnStatus GetErrorState() { return (XnStatus)m_ErrorState.GetValue(); } + XnStatus SetErrorState(XnStatus errorState); + + /** + * Resolves the config file's path. + * Specify NULL to strConfigDir to resolve it based on the driver's directory. + */ + static XnStatus ResolveGlobalConfigFileName(XnChar* strConfigFile, XnUInt32 nBufSize, const XnChar* strConfigDir); + + XnStatus SetGlobalConfigFile(const XnChar* strConfigFile); + XnStatus ConfigureModuleFromGlobalFile(const XnChar* strModule, const XnChar* strSection = NULL); + + const XnChar* GetUSBPath() { return m_SensorIO.GetDevicePath(); } + XnBool ShouldUseHostTimestamps() { return (m_HostTimestamps.GetValue() == TRUE); } + XnBool HasReadingStarted() { return (m_ReadData.GetValue() == TRUE); } + inline XnBool IsTecDebugPring() const { return (XnBool)m_FirmwareTecDebugPrint.GetValue(); } + + XnStatus SetFrameSyncStreamGroup(XnDeviceStream** ppStreamList, XnUInt32 numStreams); + +protected: + virtual XnStatus CreateStreamImpl(const XnChar* strType, const XnChar* strName, const XnActualPropertiesHash* pInitialSet); + + XnStatus CreateDeviceModule(XnDeviceModuleHolder** ppModuleHolder); + XnStatus CreateStreamModule(const XnChar* StreamType, const XnChar* StreamName, XnDeviceModuleHolder** ppStream); + void DestroyStreamModule(XnDeviceModuleHolder* pStreamHolder); + + virtual void OnNewStreamData(XnDeviceStream* pStream, OniFrame* pFrame); + +private: + XnStatus InitSensor(const XnDeviceConfig* pDeviceConfig); + XnStatus ValidateSensorID(XnChar* csSensorID); + XnStatus SetMirrorForModule(XnDeviceModule* pModule, XnUInt64 nValue); + XnStatus FindSensorStream(const XnChar* StreamName, IXnSensorStream** ppStream); + XnStatus InitReading(); + XnStatus OnFrameSyncPropertyChanged(); + + static XnStatus XN_CALLBACK_TYPE GetInstanceCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + + XnStatus ChangeTaskInterval(XnScheduledTask** ppTask, XnTaskCallbackFuncPtr pCallback, XnUInt32 nInterval); + void ReadFirmwareLog(); + void ReadFirmwareCPU(); + + //--------------------------------------------------------------------------- + // Getters + //--------------------------------------------------------------------------- + XnStatus GetFirmwareParam(XnInnerParamData* pParam); + XnStatus GetCmosBlankingUnits(XnCmosBlankingUnits* pBlanking); + XnStatus GetCmosBlankingTime(XnCmosBlankingTime* pBlanking); + XnStatus GetFirmwareMode(XnParamCurrentMode* pnMode); + XnStatus GetLastRawFrame(const XnChar* strStream, XnUChar* pBuffer, XnUInt32 nDataSize); + XnStatus GetFixedParams(XnDynamicSizeBuffer* pBuffer); + XnStatus GetDepthCmosRegister(XnControlProcessingData* pRegister); + XnStatus GetImageCmosRegister(XnControlProcessingData* pRegister); + XnStatus ReadAHB(XnAHBData* pData); + XnStatus GetI2C(XnI2CReadData* pI2CReadData); + XnStatus GetTecStatus(XnTecData* pTecData); + XnStatus GetTecFastConvergenceStatus(XnTecFastConvergenceData* pTecData); + XnStatus GetEmitterStatus(XnEmitterData* pEmitterData); + XnStatus ReadFlashFile(const XnParamFileData* pFile); + XnStatus ReadFlashChunk(XnParamFlashData* pFlash); + XnStatus GetFirmwareLog(XnChar* csLog, XnUInt32 nSize); + XnStatus GetFileList(XnFlashFileList* pFileList); + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + XnStatus SetInterface(XnSensorUsbInterface nInterface); + XnStatus SetHostTimestamps(XnBool bHostTimestamps); + XnStatus SetNumberOfBuffers(XnUInt32 nCount); + XnStatus SetReadData(XnBool bRead); + XnStatus SetFirmwareParam(const XnInnerParamData* pParam); + XnStatus SetCmosBlankingUnits(const XnCmosBlankingUnits* pBlanking); + XnStatus SetCmosBlankingTime(const XnCmosBlankingTime* pBlanking); + XnStatus Reset(XnParamResetType nType); + XnStatus SetFirmwareMode(XnParamCurrentMode nMode); + XnStatus SetDepthCmosRegister(const XnControlProcessingData* pRegister); + XnStatus SetImageCmosRegister(const XnControlProcessingData* pRegister); + XnStatus WriteAHB(const XnAHBData* pData); + XnStatus SetLedState(XnUInt16 nLedId, XnUInt16 nState); + XnStatus SetEmitterState(XnBool bActive); + XnStatus SetFirmwareFrameSync(XnBool bOn); + XnStatus SetI2C(const XnI2CWriteData* pI2CWriteData); + XnStatus SetFirmwareLogFilter(XnUInt32 nFilter); + XnStatus SetFirmwareLogInterval(XnUInt32 nMilliSeconds); + XnStatus SetFirmwareLogPrint(XnBool bPrint); + XnStatus SetFirmwareCPUInterval(XnUInt32 nMilliSeconds); + XnStatus SetAPCEnabled(XnBool bEnabled); + XnStatus DeleteFile(XnUInt16 nFileID); + XnStatus SetTecSetPoint(XnUInt16 nSetPoint); + XnStatus SetEmitterSetPoint(XnUInt16 nSetPoint); + XnStatus SetFileAttributes(const XnFileAttributes* pAttributes); + XnStatus WriteFlashFile(const XnParamFileData* pFile); + XnStatus SetProjectorFault(XnProjectorFaultData* pProjectorFaultData); + XnStatus RunBIST(XnUInt32 nTestsMask, XnUInt32* pnFailures); + + //--------------------------------------------------------------------------- + // Callbacks + //--------------------------------------------------------------------------- + static void XN_CALLBACK_TYPE OnDeviceDisconnected(const OniDeviceInfo& deviceInfo, void* pCookie); + + static XnStatus XN_CALLBACK_TYPE SetInterfaceCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetHostTimestampsCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetNumberOfBuffersCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetReadDataCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareParamCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetCmosBlankingUnitsCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetCmosBlankingTimeCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ResetCallback(XnIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareModeCallback(XnIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetFixedParamsCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE FrameSyncPropertyChangedCallback(const XnProperty* pSender, void* pCookie); + static XnBool XN_CALLBACK_TYPE HasSynchedFrameArrived(void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetFirmwareParamCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetCmosBlankingUnitsCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetCmosBlankingTimeCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetFirmwareModeCallback(const XnIntProperty* pSender, XnUInt64* pnValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetAudioSupportedCallback(const XnIntProperty* pSender, XnUInt64* pnValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetImageSupportedCallback(const XnIntProperty* pSender, XnUInt64* pnValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetDepthCmosRegisterCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetImageCmosRegisterCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetDepthCmosRegisterCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetImageCmosRegisterCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ReadAHBCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE WriteAHBCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetLedStateCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetEmitterStateCallback(XnIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareFrameSyncCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareLogFilterCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareLogIntervalCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareLogPrintCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareCPUIntervalCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetAPCEnabledCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetI2CCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE DeleteFileCallback(XnIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetTecSetPointCallback(XnIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetEmitterSetPointCallback(XnIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFileAttributesCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE WriteFlashFileCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetProjectorFaultCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE RunBISTCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetFileListCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static void XN_CALLBACK_TYPE ExecuteFirmwareLogTask(void* pCookie); + static void XN_CALLBACK_TYPE ExecuteFirmwareCPUTask(void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetI2CCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetTecStatusCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetTecFastConvergenceStatusCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetEmitterStatusCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ReadFlashFileCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetFirmwareLogCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ReadFlashChunkCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnCallbackHandle m_hDisconnectedCallback; + XnActualIntProperty m_ErrorState; + XnActualIntProperty m_ResetSensorOnStartup; + XnActualIntProperty m_LeanInit; + XnActualIntProperty m_Interface; + XnActualIntProperty m_ReadData; + XnActualIntProperty m_FrameSync; + XnActualIntProperty m_FirmwareFrameSync; + XnActualIntProperty m_CloseStreamsOnShutdown; + XnActualIntProperty m_HostTimestamps; + XnGeneralProperty m_FirmwareParam; + XnGeneralProperty m_CmosBlankingUnits; + XnGeneralProperty m_CmosBlankingTime; + XnIntProperty m_Reset; + XnVersions m_VersionData; + XnActualGeneralProperty m_Version; + XnGeneralProperty m_FixedParam; + XnActualStringProperty m_ID; + XnActualStringProperty m_DeviceName; + XnActualStringProperty m_VendorSpecificData; + XnActualStringProperty m_PlatformString; + XnIntProperty m_AudioSupported; + XnIntProperty m_ImageSupported; + XnGeneralProperty m_ImageControl; + XnGeneralProperty m_DepthControl; + XnGeneralProperty m_AHB; + XnGeneralProperty m_LedState; + XnIntProperty m_EmitterEnabled; + XnActualIntProperty m_FirmwareLogFilter; + XnActualIntProperty m_FirmwareLogInterval; + XnActualIntProperty m_FirmwareLogPrint; + XnActualIntProperty m_FirmwareCPUInterval; + XnActualIntProperty m_APCEnabled; + XnActualIntProperty m_FirmwareTecDebugPrint; + XnGeneralProperty m_I2C; + XnIntProperty m_DeleteFile; + XnIntProperty m_TecSetPoint; + XnGeneralProperty m_TecStatus; + XnGeneralProperty m_TecFastConvergenceStatus; + XnIntProperty m_EmitterSetPoint; + XnGeneralProperty m_EmitterStatus; + XnGeneralProperty m_FileAttributes; + XnGeneralProperty m_FlashFile; + XnGeneralProperty m_FirmwareLog; + XnGeneralProperty m_FlashChunk; + XnGeneralProperty m_FileList; + XnGeneralProperty m_BIST; + XnGeneralProperty m_ProjectorFault; + XnSensorFirmware m_Firmware; + XnDevicePrivateData m_DevicePrivateData; + XnSensorFPS m_FPS; + XnCmosInfo m_CmosInfo; + XnSensorIO m_SensorIO; + + XnSensorObjects m_Objects; + + /** A scheduler to be used for performing periodic tasks. */ + XnScheduler* m_pScheduler; + XnScheduledTask* m_pLogTask; + XnScheduledTask* m_pCPUTask; + XnDumpFile* m_FirmwareLogDump; + XnDumpFile* m_FrameSyncDump; + XnBool m_nFrameSyncEnabled; + typedef struct + { + XnDeviceStream* pStream; + OniFrame* pFrame; + } FrameSyncedStream; + xnl::Array m_FrameSyncedStreams; + int m_nFrameSyncLastFrameID; + xnl::CriticalSection m_frameSyncCs; + + XnBool m_bInitialized; + + XnIntPropertySynchronizer m_PropSynchronizer; + + XnChar m_strGlobalConfigFile[XN_FILE_MAX_PATH]; +}; + +#endif //__XN_SENSOR_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnSensorAudioStream.cpp b/Source/Drivers/PS1080/Sensor/XnSensorAudioStream.cpp new file mode 100644 index 0000000..8139434 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorAudioStream.cpp @@ -0,0 +1,478 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#if 0 // Audio support +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensorInit.h" +#include "XnSensorAudioStream.h" +#include "XnSensor.h" +#include "XnAudioProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_AUDIO_MAX_SAMPLE_RATE 48000 +#define XN_AUDIO_MAX_NUMBER_OF_CHANNELS 2 + +#define XN_SENSOR_PROTOCOL_AUDIO_PACKET_SIZE_BULK 424 +#define XN_SENSOR_PROTOCOL_AUDIO_PACKET_SIZE_ISO 180 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnSensorAudioStream::XnSensorAudioStream(const XnChar* strDeviceName, const XnChar* StreamName, XnSensorObjects* pObjects, XnBool bAllowOtherUsers) : + XnAudioStream(StreamName, XN_AUDIO_MAX_NUMBER_OF_CHANNELS), + m_Helper(pObjects), + m_strDeviceName(strDeviceName), + m_bAllowOtherUsers(bAllowOtherUsers), + m_LeftChannelVolume(XN_STREAM_PROPERTY_LEFT_CHANNEL_VOLUME, "LeftChannelVolume", XN_AUDIO_STREAM_DEFAULT_VOLUME), + m_RightChannelVolume(XN_STREAM_PROPERTY_RIGHT_CHANNEL_VOLUME, "RightChannelVolume", XN_AUDIO_STREAM_DEFAULT_VOLUME), + m_ActualRead(XN_STREAM_PROPERTY_ACTUAL_READ_DATA, "ActualReadData", FALSE), + m_nFrameID(0) +{ + m_buffer.pAudioBuffer = NULL; + m_LeftChannelVolume.UpdateSetCallback(SetLeftChannelVolumeCallback, this); + m_RightChannelVolume.UpdateSetCallback(SetRightChannelVolumeCallback, this); + m_ActualRead.UpdateSetCallback(SetActualReadCallback, this); +} + +XnStatus XnSensorAudioStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnAudioStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + // init helper + nRetVal = m_Helper.Init(this, this); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = xnOSCreateCriticalSection(&m_buffer.hLock); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SetReadChunkSize(XN_AUDIO_STREAM_DEFAULT_CHUNK_SIZE); + XN_IS_STATUS_OK(nRetVal); + + // add properties + XN_VALIDATE_ADD_PROPERTIES(this, &m_LeftChannelVolume, &m_RightChannelVolume, &m_ActualRead); + + // check what's the firmware audio packet size + if (m_Helper.GetPrivateData()->SensorHandle.MiscConnection.bIsISO) + m_nOrigAudioPacketSize = XN_SENSOR_PROTOCOL_AUDIO_PACKET_SIZE_ISO; + else + m_nOrigAudioPacketSize = XN_SENSOR_PROTOCOL_AUDIO_PACKET_SIZE_BULK; + + // alloc buffer + nRetVal = ReallocBuffer(); + XN_IS_STATUS_OK(nRetVal); + + m_buffer.pAudioCallback = NewDataCallback; + m_buffer.pAudioCallbackCookie = this; + + // data processor + nRetVal = m_Helper.RegisterDataProcessorProperty(NumberOfChannelsProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::Free() +{ + if (m_buffer.pAudioBuffer != NULL) + { + xnOSFreeAligned(m_buffer.pAudioBuffer); + m_buffer.pAudioBuffer = NULL; + } + + if (m_buffer.pAudioPacketsTimestamps != NULL) + { + xnOSFreeAligned(m_buffer.pAudioPacketsTimestamps); + m_buffer.pAudioPacketsTimestamps = NULL; + } + + m_Helper.Free(); + XnAudioStream::Free(); + + // close critical sections + if (m_buffer.hLock != NULL) + { + xnOSCloseCriticalSection(&m_buffer.hLock); + m_buffer.hLock = NULL; + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::MapPropertiesToFirmware() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.MapFirmwareProperty(SampleRateProperty(), GetFirmwareParams()->m_AudioSampleRate, FALSE, ConvertSampleRateToFirmwareRate); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(NumberOfChannelsProperty(), GetFirmwareParams()->m_AudioStereo, FALSE, ConvertNumberOfChannelsToStereo); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_LeftChannelVolume, GetFirmwareParams()->m_AudioLeftChannelGain, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_RightChannelVolume, GetFirmwareParams()->m_AudioRightChannelGain, TRUE); + XN_IS_STATUS_OK(nRetVal);; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::ConfigureStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificMiscUsb->pUsbConnection->UsbEp); + + nRetVal = SetActualRead(TRUE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.ConfigureFirmware(SampleRateProperty()); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(NumberOfChannelsProperty()); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_LeftChannelVolume); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_RightChannelVolume); + XN_IS_STATUS_OK(nRetVal);; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::SetActualRead(XnBool bRead) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if ((XnBool)m_ActualRead.GetValue() != bRead) + { + if (bRead) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Creating USB audio read thread..."); + XnSpecificUsbDevice* pUSB = GetHelper()->GetPrivateData()->pSpecificMiscUsb; + nRetVal = xnUSBInitReadThread(pUSB->pUsbConnection->UsbEp, pUSB->nChunkReadBytes, pUSB->nNumberOfBuffers, pUSB->nTimeout, XnDeviceSensorProtocolUsbEpCb, pUSB); + XN_IS_STATUS_OK(nRetVal); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down USB audio read thread..."); + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificMiscUsb->pUsbConnection->UsbEp); + } + + nRetVal = m_ActualRead.UnsafeUpdateValue(bRead); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::OpenStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmwareParams()->m_Stream2Mode.SetValue(XN_AUDIO_STREAM_ON); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnAudioStream::Open(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::CloseStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmwareParams()->m_Stream2Mode.SetValue(XN_AUDIO_STREAM_OFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SetActualRead(FALSE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnAudioStream::Close(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::CreateDataProcessor(XnDataProcessor** ppProcessor) +{ + XnDataProcessor* pAudioProcessor; + XN_VALIDATE_NEW_AND_INIT(pAudioProcessor, XnAudioProcessor, this, &m_Helper, &m_buffer, m_nOrigAudioPacketSize); + + *ppProcessor = pAudioProcessor; + + return XN_STATUS_OK; +} + +XnStatus XnSensorAudioStream::SetOutputFormat(OniFormat nOutputFormat) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (nOutputFormat) + { + case ONI_PIXEL_FORMAT_PCM: + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Output format %d, isn't supported by sensor audio stream!", nOutputFormat); + } + + nRetVal = XnAudioStream::SetOutputFormat(nOutputFormat); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::SetSampleRate(XnSampleRate nSampleRate) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(SampleRateProperty(), (XnUInt16)nSampleRate); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnAudioStream::SetSampleRate(nSampleRate); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(SampleRateProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::SetNumberOfChannels(XnUInt32 nNumberOfChannels) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(NumberOfChannelsProperty(), (XnUInt16)nNumberOfChannels); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnAudioStream::SetNumberOfChannels(nNumberOfChannels); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ReallocBuffer(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(NumberOfChannelsProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::NewData() +{ + // TODO: fix this +/* // check how many buffers we have + XnInt32 nAvailbalePackets = m_buffer.nAudioWriteIndex - m_buffer.nAudioReadIndex; + if (nAvailbalePackets < 0) + nAvailbalePackets += m_buffer.nAudioBufferNumOfPackets; + + if ((XnUInt32)nAvailbalePackets * m_buffer.nAudioPacketSize >= GetReadChunkSize()) + { + // update last write index (the last written byte) + m_header.nWritePacketIndex = m_buffer.nAudioWriteIndex; + // take first packet timestamp + NewDataAvailable(m_buffer.pAudioPacketsTimestamps[m_buffer.nAudioReadIndex], 0); + } +*/ + return XN_STATUS_OK; +} + +XnStatus XnSensorAudioStream::ConvertNumberOfChannelsToStereo(XnUInt64 nSource, XnUInt64* pnDest) +{ + *pnDest = (nSource == 2); + return XN_STATUS_OK; +} + +XnStatus XnSensorAudioStream::ConvertStereoToNumberOfChannels(XnUInt64 nSource, XnUInt64* pnDest) +{ + *pnDest = nSource ? 2 : 1; + return XN_STATUS_OK; +} + +XnStatus XnSensorAudioStream::ConvertSampleRateToFirmwareRate(XnUInt64 nSource, XnUInt64* pnDest) +{ + switch (nSource) + { + case XN_SAMPLE_RATE_8K: + *pnDest = A2D_SAMPLE_RATE_8KHZ; + break; + case XN_SAMPLE_RATE_11K: + *pnDest = A2D_SAMPLE_RATE_11KHZ; + break; + case XN_SAMPLE_RATE_12K: + *pnDest = A2D_SAMPLE_RATE_12KHZ; + break; + case XN_SAMPLE_RATE_16K: + *pnDest = A2D_SAMPLE_RATE_16KHZ; + break; + case XN_SAMPLE_RATE_22K: + *pnDest = A2D_SAMPLE_RATE_22KHZ; + break; + case XN_SAMPLE_RATE_24K: + *pnDest = A2D_SAMPLE_RATE_24KHZ; + break; + case XN_SAMPLE_RATE_32K: + *pnDest = A2D_SAMPLE_RATE_32KHZ; + break; + case XN_SAMPLE_RATE_44K: + *pnDest = A2D_SAMPLE_RATE_44KHZ; + break; + case XN_SAMPLE_RATE_48K: + *pnDest = A2D_SAMPLE_RATE_48KHZ; + break; + default: + return XN_STATUS_DEVICE_UNSUPPORTED_MODE; + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::ConvertFirmwareRateToSampleRate(XnUInt64 nSource, XnUInt64* pnDest) +{ + switch (nSource) + { + case A2D_SAMPLE_RATE_8KHZ: + *pnDest = XN_SAMPLE_RATE_8K; + break; + case A2D_SAMPLE_RATE_11KHZ: + *pnDest = XN_SAMPLE_RATE_11K; + break; + case A2D_SAMPLE_RATE_12KHZ: + *pnDest = XN_SAMPLE_RATE_12K; + break; + case A2D_SAMPLE_RATE_16KHZ: + *pnDest = XN_SAMPLE_RATE_16K; + break; + case A2D_SAMPLE_RATE_22KHZ: + *pnDest = XN_SAMPLE_RATE_22K; + break; + case A2D_SAMPLE_RATE_24KHZ: + *pnDest = XN_SAMPLE_RATE_24K; + break; + case A2D_SAMPLE_RATE_32KHZ: + *pnDest = XN_SAMPLE_RATE_32K; + break; + case A2D_SAMPLE_RATE_44KHZ: + *pnDest = XN_SAMPLE_RATE_44K; + break; + case A2D_SAMPLE_RATE_48KHZ: + *pnDest = XN_SAMPLE_RATE_48K; + break; + default: + return XN_STATUS_DEVICE_UNSUPPORTED_MODE; + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::SetLeftChannelVolume(XnUInt32 nVolume) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_LeftChannelVolume, (XnUInt16)nVolume); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::SetRightChannelVolume(XnUInt32 nVolume) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_RightChannelVolume, (XnUInt16)nVolume); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorAudioStream::ReallocBuffer() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_buffer.pAudioBuffer == NULL) + { + // we allocate enough for 5 seconds of audio + XnUInt32 nSampleSize = 2 * 2; // 16-bit per channel (2 bytes) * max number of channels (2) + XnUInt32 nSamples = 48000 * 5; // max sample rate * number of seconds + + XnUInt32 nMaxBufferSize = nSamples * nSampleSize; + + // find min packet size (so we'll have max packet count) + XnUInt32 nMinPacketSize = XN_MIN(XN_SENSOR_PROTOCOL_AUDIO_PACKET_SIZE_BULK, XN_SENSOR_PROTOCOL_AUDIO_PACKET_SIZE_ISO); + XnUInt32 nMaxPacketCount = nMaxBufferSize / nMinPacketSize - 1; + + nRetVal = RequiredSizeProperty().UnsafeUpdateValue(nMaxBufferSize); + XN_IS_STATUS_OK(nRetVal); + + m_buffer.pAudioPacketsTimestamps = (XnUInt64*)xnOSMallocAligned(sizeof(XnUInt64) * nMaxPacketCount, XN_DEFAULT_MEM_ALIGN); + m_buffer.pAudioBuffer = (XnUInt8*)xnOSMallocAligned(nMaxBufferSize, XN_DEFAULT_MEM_ALIGN); + m_buffer.nAudioBufferSize = nMaxBufferSize; + } + + // calculate current packet size + m_buffer.nAudioPacketSize = m_nOrigAudioPacketSize; + + if (m_Helper.GetFirmwareVersion() >= XN_SENSOR_FW_VER_5_2 && GetNumberOfChannels() == 1) + { + m_buffer.nAudioPacketSize /= 2; + } + + m_buffer.nAudioBufferNumOfPackets = m_buffer.nAudioBufferSize / m_buffer.nAudioPacketSize; + m_buffer.nAudioBufferSize = m_buffer.nAudioBufferNumOfPackets * m_buffer.nAudioPacketSize; + + m_header.nPacketCount = m_buffer.nAudioBufferNumOfPackets; + m_header.nPacketSize = m_buffer.nAudioPacketSize; + + // set read and write indices + m_buffer.nAudioReadIndex = 0; + m_buffer.nAudioWriteIndex = 0; + + return (XN_STATUS_OK); +} + +XnStatus XN_CALLBACK_TYPE XnSensorAudioStream::SetLeftChannelVolumeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorAudioStream* pThis = (XnSensorAudioStream*)pCookie; + return pThis->SetLeftChannelVolume((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorAudioStream::SetRightChannelVolumeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorAudioStream* pThis = (XnSensorAudioStream*)pCookie; + return pThis->SetRightChannelVolume((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorAudioStream::SetActualReadCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorAudioStream* pThis = (XnSensorAudioStream*)pCookie; + return pThis->SetActualRead(nValue == TRUE); +} + +XnStatus XN_CALLBACK_TYPE XnSensorAudioStream::NewDataCallback(void* pCookie) +{ + XnSensorAudioStream* pThis = (XnSensorAudioStream*)pCookie; + return pThis->NewData(); +} + +#endif // Audio support diff --git a/Source/Drivers/PS1080/Sensor/XnSensorAudioStream.h b/Source/Drivers/PS1080/Sensor/XnSensorAudioStream.h new file mode 100644 index 0000000..ab45c9d --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorAudioStream.h @@ -0,0 +1,122 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_AUDIO_STREAM_H__ +#define __XN_SENSOR_AUDIO_STREAM_H__ + +#if 0 // Audio support + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnSensorStreamHelper.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_AUDIO_STREAM_DEFAULT_VOLUME 12 +#define XN_AUDIO_STREAM_DEFAULT_SAMPLE_RATE 48000 +#define XN_AUDIO_STREAM_DEFAULT_NUMBER_OF_CHANNELS 2 +#define XN_AUDIO_STREAM_DEFAULT_OUTPUT_FORMAT ONI_PIXEL_FORMAT_PCM +#define XN_AUDIO_STREAM_DEFAULT_CHUNK_SIZE 2120 + +//--------------------------------------------------------------------------- +// XnSensorAudioStream class +//--------------------------------------------------------------------------- +class XnSensorAudioStream : public XnAudioStream, public IXnSensorStream +{ +public: + XnSensorAudioStream(const XnChar* strDeviceName, const XnChar* StreamName, XnSensorObjects* pObjects, XnBool bAllowOtherUsers); + ~XnSensorAudioStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + XnStatus Free(); + XnStatus BatchConfig(const XnActualPropertiesHash& props) { return m_Helper.BatchConfig(props); } + + inline XnSensorStreamHelper* GetHelper() { return &m_Helper; } + + friend class XnAudioProcessor; +protected: + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Open() { return m_Helper.Open(); } + XnStatus Close() { return m_Helper.Close(); } + XnStatus ConfigureStreamImpl(); + XnStatus OpenStreamImpl(); + XnStatus CloseStreamImpl(); + XnStatus CreateDataProcessor(XnDataProcessor** ppProcessor); + XnStatus MapPropertiesToFirmware(); + void GetFirmwareStreamConfig(XnResolutions* pnRes, XnUInt32* pnFPS) { *pnRes = XN_RESOLUTION_CUSTOM; *pnFPS = 0; } + + XnStatus Mirror(OniFrame* /*pFrame*/) const { return XN_STATUS_OK; } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + XnStatus SetOutputFormat(OniFormat nOutputFormat); + XnStatus SetLeftChannelVolume(XnUInt32 nVolume); + XnStatus SetRightChannelVolume(XnUInt32 nVolume); + XnStatus SetSampleRate(XnSampleRate nSampleRate); + XnStatus SetNumberOfChannels(XnUInt32 nNumberOfChannels); + XnStatus SetActualRead(XnBool bRead); + +private: + XnStatus NewData(); + XnStatus ReallocBuffer(); + + inline XnSensorFirmwareParams* GetFirmwareParams() const { return m_Helper.GetFirmware()->GetParams(); } + + static XnStatus ConvertNumberOfChannelsToStereo(XnUInt64 nSource, XnUInt64* pnDest); + static XnStatus ConvertStereoToNumberOfChannels(XnUInt64 nSource, XnUInt64* pnDest); + static XnStatus ConvertSampleRateToFirmwareRate(XnUInt64 nSource, XnUInt64* pnDest); + static XnStatus ConvertFirmwareRateToSampleRate(XnUInt64 nSource, XnUInt64* pnDest); + + static XnStatus XN_CALLBACK_TYPE SetLeftChannelVolumeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetRightChannelVolumeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetActualReadCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE NewDataCallback(void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnSensorStreamHelper m_Helper; + + XnAudioSharedBuffer m_header; + XnDeviceAudioBuffer m_buffer; + + const XnChar* m_strDeviceName; + XnBool m_bAllowOtherUsers; + XnActualIntProperty m_LeftChannelVolume; + XnActualIntProperty m_RightChannelVolume; + + XnActualIntProperty m_ActualRead; + + XnUInt32 m_nOrigAudioPacketSize; + + XnUInt32 m_nFrameID; +}; + +#endif // Audio support +#endif //__XN_SENSOR_AUDIO_STREAM_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnSensorDepthStream.cpp b/Source/Drivers/PS1080/Sensor/XnSensorDepthStream.cpp new file mode 100644 index 0000000..445eaa9 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorDepthStream.cpp @@ -0,0 +1,1291 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensorInit.h" +#include "XnSensorDepthStream.h" +#include "XnUncompressedDepthProcessor.h" +#include "XnPSCompressedDepthProcessor.h" +#include "XnPacked11DepthProcessor.h" +#include "XnPacked12DepthProcessor.h" +#include "XnCmosInfo.h" +#include +#include +#include +#include "XnSensor.h" +#include + +#include "XnGMCDebugProcessor.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_SHIFTS_MAX_SHIFT 2047 +#define XN_SHIFTS_PIXEL_SIZE_FACTOR 1 +#define XN_SHIFTS_PARAM_COEFFICIENT 4 +#define XN_SHIFTS_SHIFT_SCALE 10 + +#define XN_DEPTH_STREAM_AGC_NUMBER_OF_BINS 4 + +//--------------------------------------------------------------------------- +// XnSensorDepthStream class +//--------------------------------------------------------------------------- +XnSensorDepthStream::XnSensorDepthStream(const XnChar* strName, XnSensorObjects* pObjects) : + XnDepthStream(strName, FALSE, XN_DEVICE_SENSOR_MAX_DEPTH_1_MM, XN_SHIFTS_MAX_SHIFT), + m_Helper(pObjects), + m_InputFormat(XN_STREAM_PROPERTY_INPUT_FORMAT, "InputFormat", XN_DEPTH_STREAM_DEFAULT_INPUT_FORMAT), + m_DepthRegistration(XN_STREAM_PROPERTY_REGISTRATION, "Registration", XN_DEPTH_STREAM_DEFAULT_REGISTRATION), + m_HoleFilter(XN_STREAM_PROPERTY_HOLE_FILTER, "HoleFilter", XN_DEPTH_STREAM_DEFAULT_HOLE_FILLER), + m_WhiteBalance(XN_STREAM_PROPERTY_WHITE_BALANCE_ENABLED, "WhiteBalanceEnabled", XN_DEPTH_STREAM_DEFAULT_WHITE_BALANCE), + m_Gain(XN_STREAM_PROPERTY_GAIN, "Gain", XN_DEPTH_STREAM_DEFAULT_GAIN_OLD), + m_RegistrationType(XN_STREAM_PROPERTY_REGISTRATION_TYPE, "RegistrationType", XN_DEPTH_STREAM_DEFAULT_REGISTRATION_TYPE), + m_CroppingMode(XN_STREAM_PROPERTY_CROPPING_MODE, "CroppingMode", XN_CROPPING_MODE_NORMAL), + m_AGCBin(XN_STREAM_PROPERTY_AGC_BIN, "AGCBin", NULL, ReadAGCBinsFromFile), + m_FirmwareMirror(0, "FirmwareMirror", FALSE, strName), + m_FirmwareRegistration(0, "FirmwareRegistration", FALSE, strName), + m_FirmwareCropSizeX(0, "FirmwareCropSizeX", 0, strName), + m_FirmwareCropSizeY(0, "FirmwareCropSizeY", 0, strName), + m_FirmwareCropOffsetX(0, "FirmwareCropOffsetX", 0, strName), + m_FirmwareCropOffsetY(0, "FirmwareCropOffsetY", 0, strName), + m_FirmwareCropMode(0, "FirmwareCropMode", XN_FIRMWARE_CROPPING_MODE_DISABLED, strName), + m_ActualRead(XN_STREAM_PROPERTY_ACTUAL_READ_DATA, "ActualReadData", FALSE), + m_GMCMode(XN_STREAM_PROPERTY_GMC_MODE, "GMCMode", XN_DEPTH_STREAM_DEFAULT_GMC_MODE), + m_CloseRange(XN_STREAM_PROPERTY_CLOSE_RANGE, "CloseRange", XN_DEPTH_STREAM_DEFAULT_CLOSE_RANGE), + m_PixelRegistration(XN_STREAM_PROPERTY_PIXEL_REGISTRATION, "PixelRegistration"), + m_HorizontalFOV(ONI_STREAM_PROPERTY_HORIZONTAL_FOV, "HorizontalFov"), + m_VerticalFOV(ONI_STREAM_PROPERTY_VERTICAL_FOV, "VerticalFov"), + m_GMCDebug(XN_STREAM_PROPERTY_GMC_DEBUG, "GMCDebug", XN_DEPTH_STREAM_DEFAULT_GMC_DEBUG), + m_WavelengthCorrection(XN_STREAM_PROPERTY_WAVELENGTH_CORRECTION, "WavelengthCorrection", XN_DEPTH_STREAM_DEFAULT_WAVELENGTH_CORRECTION), + m_WavelengthCorrectionDebug(XN_STREAM_PROPERTY_WAVELENGTH_CORRECTION_DEBUG, "WavelengthCorrectionDebug", XN_DEPTH_STREAM_DEFAULT_WAVELENGTH_CORRECTION_DEBUG), + m_depthUtilsHandle(NULL), + m_hReferenceSizeChangedCallback(NULL) +{ + m_ActualRead.UpdateSetCallback(SetActualReadCallback, this); +} + +XnStatus XnSensorDepthStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init base + nRetVal = XnDepthStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + // start with no cut-off + nRetVal = MaxDepthProperty().UnsafeUpdateValue(XN_DEVICE_SENSOR_MAX_DEPTH_100_UM); + XN_IS_STATUS_OK(nRetVal); + + m_InputFormat.UpdateSetCallback(SetInputFormatCallback, this); + m_DepthRegistration.UpdateSetCallback(SetRegistrationCallback, this); + m_HoleFilter.UpdateSetCallback(SetHoleFilterCallback, this); + m_WhiteBalance.UpdateSetCallback(SetWhiteBalanceCallback, this); + m_Gain.UpdateSetCallback(SetGainCallback, this); + m_RegistrationType.UpdateSetCallback(SetRegistrationTypeCallback, this); + m_AGCBin.UpdateSetCallback(SetAGCBinCallback, this); + m_AGCBin.UpdateGetCallback(GetAGCBinCallback, this); + m_GMCMode.UpdateSetCallback(SetGMCModeCallback, this); + m_CloseRange.UpdateSetCallback(SetCloseRangeCallback, this); + m_CroppingMode.UpdateSetCallback(SetCroppingModeCallback, this); + m_PixelRegistration.UpdateGetCallback(GetPixelRegistrationCallback, this); + m_GMCDebug.UpdateSetCallback(SetGMCDebugCallback, this); + m_WavelengthCorrection.UpdateSetCallback(SetWavelengthCorrectionCallback, this); + m_WavelengthCorrectionDebug.UpdateSetCallback(SetWavelengthCorrectionDebugCallback, this); + + XN_VALIDATE_ADD_PROPERTIES(this, &m_InputFormat, &m_DepthRegistration, &m_HoleFilter, + &m_WhiteBalance, &m_Gain, &m_AGCBin, &m_ActualRead, &m_GMCMode, + &m_CloseRange, &m_CroppingMode, &m_RegistrationType, &m_PixelRegistration, + &m_HorizontalFOV, &m_VerticalFOV, &m_GMCDebug, &m_WavelengthCorrection, &m_WavelengthCorrectionDebug); + + // register supported modes + XnCmosPreset* pSupportedModes = m_Helper.GetPrivateData()->FWInfo.depthModes.GetData(); + XnUInt32 nSupportedModes = m_Helper.GetPrivateData()->FWInfo.depthModes.GetSize(); + nRetVal = AddSupportedModes(pSupportedModes, nSupportedModes); + XN_IS_STATUS_OK(nRetVal); + + if (m_Helper.GetPrivateData()->pSensor->IsLowBandwidth()) + { + nRetVal = m_InputFormat.UnsafeUpdateValue(XN_IO_DEPTH_FORMAT_COMPRESSED_PS); + XN_IS_STATUS_OK(nRetVal); + } + + // make sure default resolution is supported + XnBool bResSupported = FALSE; + for (XnUInt8 i = 0; i < nSupportedModes; ++i) + { + if (pSupportedModes[i].nResolution == XN_DEPTH_STREAM_DEFAULT_RESOLUTION) + { + bResSupported = TRUE; + break; + } + } + + XnUInt64 nDefaultResolution = XN_DEPTH_STREAM_DEFAULT_RESOLUTION; + if (!bResSupported) + { + // QVGA was always supported + nDefaultResolution = XN_RESOLUTION_QVGA; + } + + nRetVal = ResolutionProperty().UnsafeUpdateValue(nDefaultResolution); + XN_IS_STATUS_OK(nRetVal); + + // set other properties default values + nRetVal = FPSProperty().UnsafeUpdateValue(XN_DEPTH_STREAM_DEFAULT_FPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = OutputFormatProperty().UnsafeUpdateValue(XN_DEPTH_STREAM_DEFAULT_OUTPUT_FORMAT); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ParamCoefficientProperty().UnsafeUpdateValue(XN_SHIFTS_PARAM_COEFFICIENT); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ShiftScaleProperty().UnsafeUpdateValue(XN_SHIFTS_SHIFT_SCALE); + XN_IS_STATUS_OK(nRetVal); + + // read some data from firmware + XnDepthInformation DepthInformation; + nRetVal = XnHostProtocolAlgorithmParams(m_Helper.GetPrivateData(), XN_HOST_PROTOCOL_ALGORITHM_DEPTH_INFO, &DepthInformation, sizeof(XnDepthInformation), XN_RESOLUTION_VGA, 30); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ConstShiftProperty().UnsafeUpdateValue(DepthInformation.nConstShift); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ZeroPlaneDistanceProperty().UnsafeUpdateValue(m_Helper.GetFixedParams()->GetZeroPlaneDistance()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ZeroPlanePixelSizeProperty().UnsafeUpdateValue(m_Helper.GetFixedParams()->GetZeroPlanePixelSize()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = EmitterDCmosDistanceProperty().UnsafeUpdateValue(m_Helper.GetFixedParams()->GetEmitterDCmosDistance()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetDCmosRCmosDistanceProperty().UnsafeUpdateValue(m_Helper.GetFixedParams()->GetDCmosRCmosDistance()); + XN_IS_STATUS_OK(nRetVal); + + XnDouble fZPPS = m_Helper.GetFixedParams()->GetZeroPlanePixelSize(); + XnInt nZPD = m_Helper.GetFixedParams()->GetZeroPlaneDistance(); + + nRetVal = m_HorizontalFOV.UnsafeUpdateValue(2*atan(fZPPS*XN_SXGA_X_RES/2/nZPD)); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_VerticalFOV.UnsafeUpdateValue(2*atan(fZPPS*XN_VGA_Y_RES*2/2/nZPD)); + XN_IS_STATUS_OK(nRetVal); + + // init helper + nRetVal = m_Helper.Init(this, this); + XN_IS_STATUS_OK(nRetVal); + + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_3_0) + { + nRetVal = m_GMCMode.UnsafeUpdateValue(FALSE); + XN_IS_STATUS_OK(nRetVal); + } + + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_5_2) + { + nRetVal = m_WavelengthCorrection.UnsafeUpdateValue(FALSE); + XN_IS_STATUS_OK(nRetVal); + } + + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_4_0) + { + nRetVal = m_WhiteBalance.UnsafeUpdateValue(FALSE); + XN_IS_STATUS_OK(nRetVal); + } + + // on old firmwares, the host decides on the default gain. On new firmwares, we read it from firmware + if (m_Helper.GetFirmware()->GetInfo()->nFWVer > XN_SENSOR_FW_VER_1_2) + { + nRetVal = m_Gain.UnsafeUpdateValue(GetFirmwareParams()->m_DepthGain.GetValue()); + XN_IS_STATUS_OK(nRetVal); + } + + // registration + XnCallbackHandle hCallbackDummy; + nRetVal = ResolutionProperty().OnChangeEvent().Register(DecideFirmwareRegistrationCallback, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = DecideFirmwareRegistration((XnBool)m_DepthRegistration.GetValue(), (XnProcessingType)m_RegistrationType.GetValue(), GetResolution()); + XN_IS_STATUS_OK(nRetVal); + + // data processor + nRetVal = m_Helper.RegisterDataProcessorProperty(m_InputFormat); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.RegisterDataProcessorProperty(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal); + + // pixel size factor + nRetVal = GetFirmwareParams()->m_ReferenceResolution.OnChangeEvent().Register(DecidePixelSizeFactorCallback, this, m_hReferenceSizeChangedCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = DecidePixelSizeFactor(); + XN_IS_STATUS_OK(nRetVal); + + // initialize registration + if (m_Helper.GetFirmwareVersion() > XN_SENSOR_FW_VER_5_3) + { + nRetVal = PopulateSensorCalibrationInfo(); + XN_IS_STATUS_OK(nRetVal); + nRetVal = DepthUtilsInitialize(&m_calibrationInfo, &m_depthUtilsHandle); + XN_IS_STATUS_OK(nRetVal); + nRetVal = DepthUtilsSetDepthConfiguration(m_depthUtilsHandle, GetXRes(), GetYRes(), GetOutputFormat(), IsMirrored()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::Free() +{ + DepthUtilsShutdown(&m_depthUtilsHandle); + + // unregister from external properties (internal ones will be destroyed anyway...) + if (m_hReferenceSizeChangedCallback != NULL) + { + GetFirmwareParams()->m_ReferenceResolution.OnChangeEvent().Unregister(m_hReferenceSizeChangedCallback); + m_hReferenceSizeChangedCallback = NULL; + } + + m_Helper.Free(); + + XnDepthStream::Free(); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::MapPropertiesToFirmware() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.MapFirmwareProperty(m_InputFormat, GetFirmwareParams()->m_DepthFormat, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(ResolutionProperty(), GetFirmwareParams()->m_DepthResolution, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(FPSProperty(), GetFirmwareParams()->m_DepthFPS, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_HoleFilter, GetFirmwareParams()->m_DepthHoleFilter, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_Gain, GetFirmwareParams()->m_DepthGain, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_WhiteBalance, GetFirmwareParams()->m_DepthWhiteBalance, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareMirror, GetFirmwareParams()->m_DepthMirror, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareRegistration, GetFirmwareParams()->m_RegistrationEnabled, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropSizeX, GetFirmwareParams()->m_DepthCropSizeX, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropSizeY, GetFirmwareParams()->m_DepthCropSizeY, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropOffsetX, GetFirmwareParams()->m_DepthCropOffsetX, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropOffsetY, GetFirmwareParams()->m_DepthCropOffsetY, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropMode, GetFirmwareParams()->m_DepthCropMode, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_GMCMode, GetFirmwareParams()->m_GMCMode, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_CloseRange, GetFirmwareParams()->m_DepthCloseRange, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_GMCDebug, GetFirmwareParams()->m_GMCDebug, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_WavelengthCorrection, GetFirmwareParams()->m_WavelengthCorrection, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_WavelengthCorrectionDebug, GetFirmwareParams()->m_WavelengthCorrectionDebug, TRUE); + XN_IS_STATUS_OK(nRetVal);; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::ConfigureStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificDepthUsb->pUsbConnection->UsbEp); + + nRetVal = SetActualRead(TRUE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.ConfigureFirmware(m_InputFormat); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(FPSProperty()); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_HoleFilter); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_Gain); + XN_IS_STATUS_OK(nRetVal);; + + // we need to turn decimation on when resolution is QVGA, and FPS is different than 60 + // NOTE: this is ugly as hell. This logic should be moved to firmware. + XnBool bDecimation = (GetResolution() == XN_RESOLUTION_QVGA && GetFPS() != 60); + nRetVal = GetFirmwareParams()->m_DepthDecimation.SetValue(bDecimation); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareRegistration); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareMirror); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_GMCMode); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_GMCDebug); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_WavelengthCorrection); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_WavelengthCorrectionDebug); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_WhiteBalance); + XN_IS_STATUS_OK(nRetVal);; + + nRetVal = m_Helper.GetCmosInfo()->SetCmosConfig(XN_CMOS_TYPE_DEPTH, GetResolution(), GetFPS()); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnSensorDepthStream::SetActualRead(XnBool bRead) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if ((XnBool)m_ActualRead.GetValue() != bRead) + { + if (bRead) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Creating USB depth read thread..."); + XnSpecificUsbDevice* pUSB = GetHelper()->GetPrivateData()->pSpecificDepthUsb; + nRetVal = xnUSBInitReadThread(pUSB->pUsbConnection->UsbEp, pUSB->nChunkReadBytes, pUSB->nNumberOfBuffers, pUSB->nTimeout, XnDeviceSensorProtocolUsbEpCb, pUSB); + XN_IS_STATUS_OK(nRetVal); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down USB depth read thread..."); + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificDepthUsb->pUsbConnection->UsbEp); + } + + nRetVal = m_ActualRead.UnsafeUpdateValue(bRead); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::OpenStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // Turn stream on + nRetVal = GetFirmwareParams()->m_Stream1Mode.SetValue(XN_VIDEO_STREAM_DEPTH); + XN_IS_STATUS_OK(nRetVal); + + // CloseRange + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_5_6) + { + CloseRangeControl((XnBool)m_CloseRange.GetValue()); + } + else + { + nRetVal = m_Helper.ConfigureFirmware(m_CloseRange); + XN_IS_STATUS_OK(nRetVal); + } + + // Cropping + if (m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropSizeX); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropSizeY); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropOffsetX); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropOffsetY); + XN_IS_STATUS_OK(nRetVal);; + } + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropMode); + XN_IS_STATUS_OK(nRetVal);; + + nRetVal = XnDepthStream::Open(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::CloseStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmwareParams()->m_Stream1Mode.SetValue(XN_VIDEO_STREAM_OFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SetActualRead(FALSE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnDepthStream::Close(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetOutputFormat(OniPixelFormat nOutputFormat) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (nOutputFormat) + { + case ONI_PIXEL_FORMAT_SHIFT_9_2: + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + nRetVal = DeviceMaxDepthProperty().UnsafeUpdateValue(XN_DEVICE_SENSOR_MAX_DEPTH_1_MM); + break; + case ONI_PIXEL_FORMAT_DEPTH_100_UM: + nRetVal = DeviceMaxDepthProperty().UnsafeUpdateValue(XN_DEVICE_SENSOR_MAX_DEPTH_100_UM); + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Unsupported depth output format: %d", nOutputFormat); + } + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.BeforeSettingDataProcessorProperty(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnDepthStream::SetOutputFormat(nOutputFormat); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingDataProcessorProperty(); + XN_IS_STATUS_OK(nRetVal); + + if (m_depthUtilsHandle != NULL) + { + nRetVal = DepthUtilsSetDepthConfiguration(m_depthUtilsHandle, GetXRes(), GetYRes(), GetOutputFormat(), IsMirrored()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetMirror(XnBool bIsMirrored) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnOSEnterCriticalSection(GetLock()); + + // set firmware mirror + XnBool bFirmwareMirror = (bIsMirrored == TRUE && m_Helper.GetFirmwareVersion() >= XN_SENSOR_FW_VER_5_0); + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareMirror, (XnUInt16)bFirmwareMirror); + if (nRetVal != XN_STATUS_OK) + { + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + // update prop + nRetVal = XnDepthStream::SetMirror(bIsMirrored); + + xnOSLeaveCriticalSection(GetLock()); + XN_IS_STATUS_OK(nRetVal); + + if (m_depthUtilsHandle != NULL) + { + nRetVal = DepthUtilsSetDepthConfiguration(m_depthUtilsHandle, GetXRes(), GetYRes(), GetOutputFormat(), IsMirrored()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetFPS(XnUInt32 nFPS) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(FPSProperty(), (XnUInt16)nFPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnDepthStream::SetFPS(nFPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(FPSProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetResolution(XnResolutions nResolution) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(ResolutionProperty(), (XnUInt16)nResolution); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnDepthStream::SetResolution(nResolution); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal); + + if (m_depthUtilsHandle != NULL) + { + nRetVal = DepthUtilsSetDepthConfiguration(m_depthUtilsHandle, GetXRes(), GetYRes(), GetOutputFormat(), IsMirrored()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetInputFormat(XnIODepthFormats nInputFormat) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (nInputFormat) + { + case XN_IO_DEPTH_FORMAT_COMPRESSED_PS: + case XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT: + break; + case XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT: + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_4_0) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_UNSUPPORTED_MODE, XN_MASK_DEVICE_SENSOR, "11-bit depth is not supported on this sensor!"); + } + break; + case XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT: + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_4_0) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_UNSUPPORTED_MODE, XN_MASK_DEVICE_SENSOR, "12-bit depth is not supported on this sensor!"); + } + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Unknown depth input format: %d", nInputFormat); + } + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_InputFormat, (XnUInt16)nInputFormat); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetRegistration(XnBool bRegistration) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (bRegistration != (XnBool)m_DepthRegistration.GetValue()) + { + nRetVal = DecideFirmwareRegistration(bRegistration, (XnProcessingType)m_RegistrationType.GetValue(), GetResolution()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_DepthRegistration.UnsafeUpdateValue(bRegistration); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetHoleFilter(XnBool bHoleFilter) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_HoleFilter, (XnUInt16)bHoleFilter); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetWhiteBalance(XnBool bWhiteBalance) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_WhiteBalance, (XnUInt16)bWhiteBalance); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetGain(XnUInt32 nGain) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_Gain, (XnUInt16)nGain); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetRegistrationType(XnProcessingType type) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (type != m_RegistrationType.GetValue()) + { + nRetVal = DecideFirmwareRegistration((XnBool)m_DepthRegistration.GetValue(), type, GetResolution()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_RegistrationType.UnsafeUpdateValue(type); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetGMCMode(XnBool bGMCMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_GMCMode, (XnUInt16)bGMCMode); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetCloseRange(XnBool bCloseRange) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_5_6) + { + CloseRangeControl(bCloseRange); + + nRetVal = m_CloseRange.UnsafeUpdateValue(bCloseRange); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_CloseRange, (XnUInt16)bCloseRange); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetCroppingMode(XnCroppingMode mode) +{ + switch (mode) + { + case XN_CROPPING_MODE_NORMAL: + case XN_CROPPING_MODE_INCREASED_FPS: + case XN_CROPPING_MODE_SOFTWARE_ONLY: + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Bad cropping mode: %u", mode); + } + + return SetCroppingImpl(GetCropping(), mode); +} + +XnStatus XnSensorDepthStream::SetGMCDebug(XnBool bGMCDebug) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_GMCDebug, (XnUInt16)bGMCDebug); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetWavelengthCorrection(XnBool bWavelengthCorrection) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_WavelengthCorrection, (XnUInt16)bWavelengthCorrection); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetWavelengthCorrectionDebug(XnBool bWavelengthCorrectionDebug) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_WavelengthCorrectionDebug, (XnUInt16)bWavelengthCorrectionDebug); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetAGCBin(const XnDepthAGCBin* pBin) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = ValidateDepthValue(pBin->nMin); + XN_IS_STATUS_OK(nRetVal); + nRetVal = ValidateDepthValue(pBin->nMax); + XN_IS_STATUS_OK(nRetVal); + + // translate to shifts + XnUInt16* pDepthToShift = GetDepthToShiftTable(); + + XnUInt16 nMinShift = pDepthToShift[pBin->nMin]; + XnUInt16 nMaxShift = pDepthToShift[pBin->nMax]; + + // update firmware + nRetVal = XnHostProtocolSetDepthAGCBin(m_Helper.GetPrivateData(), pBin->nBin, nMinShift, nMaxShift); + XN_IS_STATUS_OK(nRetVal); + + // update prop + nRetVal = m_AGCBin.UnsafeUpdateValue(XnGeneralBufferPack((void*)pBin, sizeof(XnDepthAGCBin))); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::GetAGCBin(XnDepthAGCBin* pBin) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get from firmware + XnUInt16 nMinShift; + XnUInt16 nMaxShift; + + nRetVal = XnHostProtocolGetDepthAGCBin(m_Helper.GetPrivateData(), pBin->nBin, &nMinShift, &nMaxShift); + XN_IS_STATUS_OK(nRetVal); + + // translate to depth + OniDepthPixel* pShiftToDepth = GetShiftToDepthTable(); + + pBin->nMin = pShiftToDepth[nMinShift]; + pBin->nMax = pShiftToDepth[nMaxShift]; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetCroppingImpl(const OniCropping* pCropping, XnCroppingMode mode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFirmwareCroppingMode firmwareMode = m_Helper.GetFirmwareCroppingMode(mode, pCropping->enabled); + + nRetVal = ValidateCropping(pCropping); + XN_IS_STATUS_OK(nRetVal); + + xnOSEnterCriticalSection(GetLock()); + + if (m_Helper.GetFirmwareVersion() > XN_SENSOR_FW_VER_3_0) + { + nRetVal = m_Helper.StartFirmwareTransaction(); + if (nRetVal != XN_STATUS_OK) + { + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + if (pCropping->enabled) + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropSizeX, (XnUInt16) pCropping->width); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropSizeY, (XnUInt16) pCropping->height); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropOffsetX, (XnUInt16) pCropping->originX); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropOffsetY, (XnUInt16) pCropping->originY); + } + + if (nRetVal == XN_STATUS_OK) + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropMode, (XnUInt16)firmwareMode); + } + + if (nRetVal != XN_STATUS_OK) + { + m_Helper.RollbackFirmwareTransaction(); + m_Helper.UpdateFromFirmware(m_FirmwareCropMode); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetX); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetY); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeX); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeY); + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + nRetVal = m_Helper.CommitFirmwareTransactionAsBatch(); + if (nRetVal != XN_STATUS_OK) + { + m_Helper.UpdateFromFirmware(m_FirmwareCropMode); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetX); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetY); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeX); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeY); + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + } + + nRetVal = m_CroppingMode.UnsafeUpdateValue(mode); + XN_ASSERT(nRetVal == XN_STATUS_OK); + + nRetVal = XnDepthStream::SetCropping(pCropping); + xnOSLeaveCriticalSection(GetLock()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::CloseRangeControl(XnBool bEnabled) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (bEnabled) + { + nRetVal = XnHostProtocolWriteAHB(m_Helper.GetPrivateData(), 0x2a0038d4, 0x0, 0xFFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolWriteAHB(m_Helper.GetPrivateData(), 0x2a003820, 0x00001009, 0xFFFFFFFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_Gain, 1); + XN_IS_STATUS_OK(nRetVal); + } + else + { + if (m_CloseRange.GetValue() == TRUE) + { + nRetVal = XnHostProtocolWriteAHB(m_Helper.GetPrivateData(), 0x2a0038d4, 0x190, 0xFFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolWriteAHB(m_Helper.GetPrivateData(), 0x2a003820, 0x00001051, 0xFFFFFFFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_Gain, 42); + XN_IS_STATUS_OK(nRetVal); + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::SetCropping(const OniCropping* pCropping) +{ + return SetCroppingImpl(pCropping, (XnCroppingMode)m_CroppingMode.GetValue()); +} + +XnStatus XnSensorDepthStream::CropImpl(OniFrame* pFrame, const OniCropping* pCropping) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // if firmware cropping is disabled, crop + if (m_FirmwareCropMode.GetValue() == XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nRetVal = XnDepthStream::CropImpl(pFrame, pCropping); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::Mirror(OniFrame* pFrame) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + // only perform mirror if it's our job. if mirror is performed by FW, we don't need to do anything. + if (m_FirmwareMirror.GetValue() == FALSE) + { + nRetVal = XnDepthStream::Mirror(pFrame); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::CreateDataProcessor(XnDataProcessor** ppProcessor) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFrameBufferManager* pBufferManager; + nRetVal = StartBufferManager(&pBufferManager); + XN_IS_STATUS_OK(nRetVal); + + XnStreamProcessor* pNew; + + switch (m_InputFormat.GetValue()) + { + case XN_IO_DEPTH_FORMAT_UNCOMPRESSED_16_BIT: + XN_VALIDATE_NEW_AND_INIT(pNew, XnUncompressedDepthProcessor, this, &m_Helper, pBufferManager); + break; + case XN_IO_DEPTH_FORMAT_COMPRESSED_PS: + XN_VALIDATE_NEW_AND_INIT(pNew, XnPSCompressedDepthProcessor, this, &m_Helper, pBufferManager); + break; + case XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT: + XN_VALIDATE_NEW_AND_INIT(pNew, XnPacked11DepthProcessor, this, &m_Helper, pBufferManager); + break; + case XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT: + XN_VALIDATE_NEW_AND_INIT(pNew, XnPacked12DepthProcessor, this, &m_Helper, pBufferManager); + break; + default: + return XN_STATUS_IO_INVALID_STREAM_DEPTH_FORMAT; + } + + *ppProcessor = pNew; + + return XN_STATUS_OK; +} + +XnStatus XnSensorDepthStream::DecideFirmwareRegistration(XnBool bRegistration, XnProcessingType registrationType, XnResolutions nRes) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // start with request + XnBool bFirmwareRegistration = bRegistration; + + if (bFirmwareRegistration) + { + // old chip (PS1000) does not support registration for VGA + XnBool bHardwareRegistrationSupported = + m_Helper.GetPrivateData()->ChipInfo.nChipVer != XN_SENSOR_CHIP_VER_PS1000 || nRes == XN_RESOLUTION_QVGA; + + switch (registrationType) + { + case XN_PROCESSING_HARDWARE: + if (!bHardwareRegistrationSupported) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Sensor does not support hardware registration for current configuration!"); + } + break; + case XN_PROCESSING_SOFTWARE: + if (GetFPS() == 60) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Software registration is not supported in 60 FPS mode!"); + } + bFirmwareRegistration = FALSE; + break; + case XN_PROCESSING_DONT_CARE: + bFirmwareRegistration = bHardwareRegistrationSupported; + break; + default: + XN_LOG_ERROR_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Unknown registration type: %d", registrationType); + } + } + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareRegistration, (XnUInt16)bFirmwareRegistration); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::DecidePixelSizeFactor() +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt32 nPixelSizeFactor; + switch (GetFirmwareParams()->m_ReferenceResolution.GetValue()) + { + case XN_RESOLUTION_SXGA: + nPixelSizeFactor = 1; + break; + case XN_RESOLUTION_VGA: + nPixelSizeFactor = 2; + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DEVICE_SENSOR, "Can't resolve pixel size for reference resolution %llu", GetFirmwareParams()->m_ReferenceResolution.GetValue()); + } + + if (m_Helper.GetFirmwareVersion() < XN_SENSOR_FW_VER_3_0) + { + // due to some weird bug (we don't know the reason), DevKits older than 3.0 uses + // a smaller pixel size, but const shift remains the same. To work around this bug, + // we will just update pixel size, instead of updating pixel size factor (so that const + // shift will not be updated) + nRetVal = ZeroPlanePixelSizeProperty().UnsafeUpdateValue(m_Helper.GetFixedParams()->GetZeroPlanePixelSize() * nPixelSizeFactor); + XN_IS_STATUS_OK(nRetVal); + } + else + { + PixelSizeFactorProperty().UnsafeUpdateValue(nPixelSizeFactor); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorDepthStream::GetImageCoordinatesOfDepthPixel(XnUInt32 x, XnUInt32 y, OniDepthPixel z, XnUInt32 imageXRes, XnUInt32 imageYRes, XnUInt32& imageX, XnUInt32& imageY) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = DepthUtilsSetColorResolution(m_depthUtilsHandle, imageXRes, imageYRes); + XN_IS_STATUS_OK(nRetVal); + + // first translate to same resolution + nRetVal = DepthUtilsTranslatePixel(m_depthUtilsHandle, x, y, z, &imageX, &imageY); + return nRetVal; +} + +OniStatus XnSensorDepthStream::GetSensorCalibrationInfo(void* data, int* pDataSize) +{ + if ((size_t)*pDataSize < sizeof(DepthUtilsSensorCalibrationInfo)) + { + return ONI_STATUS_BAD_PARAMETER; + } + + if (m_depthUtilsHandle == NULL) + { + // Depth utils not initialized - probably not supported for this device + return ONI_STATUS_NOT_SUPPORTED; + } + + *pDataSize = sizeof(DepthUtilsSensorCalibrationInfo); + xnOSMemCopy(data, &m_calibrationInfo, sizeof(DepthUtilsSensorCalibrationInfo)); + return ONI_STATUS_OK; +} + + +XnStatus XnSensorDepthStream::ApplyRegistration(OniDepthPixel* pDetphmap) +{ + DepthUtilsTranslateDepthMap(m_depthUtilsHandle, pDetphmap); + return XN_STATUS_OK; +} + +#define RGB_REG_X_RES 640 +#define RGB_REG_Y_RES 512 +#define XN_CMOS_VGAOUTPUT_XRES 1280 +#define XN_SENSOR_WIN_OFFET_X 1 +#define XN_SENSOR_WIN_OFFET_Y 1 +#define RGB_REG_X_VAL_SCALE 16 +#define S2D_PEL_CONST 10 +#define S2D_CONST_OFFSET 0.375 + +XnStatus XnSensorDepthStream::PopulateSensorCalibrationInfo() +{ + XnDouble dPlanePixelSize; + GetProperty(XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE, &dPlanePixelSize); + + XnUInt64 nPlaneDsr; + GetProperty(XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE, &nPlaneDsr); + + XnDouble dDCRCDist; + GetProperty(XN_STREAM_PROPERTY_DCMOS_RCMOS_DISTANCE, &dDCRCDist); + + m_calibrationInfo.magic = ONI_DEPTH_UTILS_CALIBRATION_INFO_MAGIC; + m_calibrationInfo.version = 1; + m_calibrationInfo.params1080.zpd = (int)nPlaneDsr; + m_calibrationInfo.params1080.zpps = dPlanePixelSize; + m_calibrationInfo.params1080.dcrcdist = dDCRCDist; + + xnOSStrCopy(m_calibrationInfo.deviceName, "PS1080", 80); + xnOSMemSet(m_calibrationInfo.serial, 0, 80); + + m_calibrationInfo.params1080.rgbRegXRes = RGB_REG_X_RES; + m_calibrationInfo.params1080.rgbRegYRes = RGB_REG_Y_RES; + m_calibrationInfo.params1080.cmosVGAOutputXRes = XN_CMOS_VGAOUTPUT_XRES; + m_calibrationInfo.params1080.sensorWinOffsetX = XN_SENSOR_WIN_OFFET_X; + m_calibrationInfo.params1080.sensorWinOffsetY = XN_SENSOR_WIN_OFFET_Y; + m_calibrationInfo.params1080.rgbRegXValScale = RGB_REG_X_VAL_SCALE; + m_calibrationInfo.params1080.s2dPelConst = S2D_PEL_CONST; + m_calibrationInfo.params1080.s2dConstOffset = S2D_CONST_OFFSET; + + XnStatus nRetVal = XnHostProtocolAlgorithmParams(m_Helper.GetPrivateData(), XN_HOST_PROTOCOL_ALGORITHM_REGISTRATION, &m_calibrationInfo.params1080.registrationInfo_QQVGA, sizeof(m_calibrationInfo.params1080.registrationInfo_QQVGA), XN_RESOLUTION_QQVGA, 30); + if (nRetVal != XN_STATUS_OK) + { + xnOSMemSet(&m_calibrationInfo.params1080.registrationInfo_QQVGA, 0, sizeof(m_calibrationInfo.params1080.registrationInfo_QQVGA)); + } + + nRetVal = XnHostProtocolAlgorithmParams(m_Helper.GetPrivateData(), XN_HOST_PROTOCOL_ALGORITHM_REGISTRATION, &m_calibrationInfo.params1080.registrationInfo_QVGA, sizeof(m_calibrationInfo.params1080.registrationInfo_QVGA), XN_RESOLUTION_QVGA, 30); + XN_IS_STATUS_OK(nRetVal); + nRetVal = XnHostProtocolAlgorithmParams(m_Helper.GetPrivateData(), XN_HOST_PROTOCOL_ALGORITHM_REGISTRATION, &m_calibrationInfo.params1080.registrationInfo_VGA, sizeof(m_calibrationInfo.params1080.registrationInfo_VGA), XN_RESOLUTION_VGA, 30); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolAlgorithmParams(m_Helper.GetPrivateData(), XN_HOST_PROTOCOL_ALGORITHM_PADDING, &m_calibrationInfo.params1080.padInfo_QQVGA, sizeof(m_calibrationInfo.params1080.padInfo_QQVGA), XN_RESOLUTION_QQVGA, 30); + if (nRetVal != XN_STATUS_OK) + { + xnOSMemSet(&m_calibrationInfo.params1080.padInfo_QQVGA, 0, sizeof(m_calibrationInfo.params1080.padInfo_QQVGA)); + } + + xnOSMemSet(&m_calibrationInfo.params1080.padInfo_QVGA, 0, sizeof(m_calibrationInfo.params1080.padInfo_QVGA)); + nRetVal = XnHostProtocolAlgorithmParams(m_Helper.GetPrivateData(), XN_HOST_PROTOCOL_ALGORITHM_PADDING, &m_calibrationInfo.params1080.padInfo_QVGA, sizeof(m_calibrationInfo.params1080.padInfo_QVGA), XN_RESOLUTION_QVGA, 30); + XN_IS_STATUS_OK(nRetVal); + xnOSMemSet(&m_calibrationInfo.params1080.padInfo_VGA, 0, sizeof(m_calibrationInfo.params1080.padInfo_VGA)); + nRetVal = XnHostProtocolAlgorithmParams(m_Helper.GetPrivateData(), XN_HOST_PROTOCOL_ALGORITHM_PADDING, &m_calibrationInfo.params1080.padInfo_VGA, sizeof(m_calibrationInfo.params1080.padInfo_VGA), XN_RESOLUTION_VGA, 30); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetInputFormatCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetInputFormat((XnIODepthFormats)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetRegistrationCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetRegistration((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetHoleFilterCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetHoleFilter((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetWhiteBalanceCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetWhiteBalance((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetGainCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetGain((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetRegistrationTypeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetRegistrationType((XnProcessingType)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetGMCModeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetGMCMode((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetCloseRangeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetCloseRange((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetCroppingModeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetCroppingMode((XnCroppingMode)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetGMCDebugCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetGMCDebug((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetWavelengthCorrectionCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetWavelengthCorrection((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetWavelengthCorrectionDebugCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetWavelengthCorrectionDebug((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetAGCBinCallback(XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + if (gbValue.dataSize != sizeof(XnDepthAGCBin)) + { + return XN_STATUS_DEVICE_PROPERTY_SIZE_DONT_MATCH; + } + + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->SetAGCBin((XnDepthAGCBin*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::GetAGCBinCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + if (gbValue.dataSize != sizeof(XnDepthAGCBin)) + { + return XN_STATUS_DEVICE_PROPERTY_SIZE_DONT_MATCH; + } + + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->GetAGCBin((XnDepthAGCBin*)gbValue.data); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::SetActualReadCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorDepthStream* pThis = (XnSensorDepthStream*)pCookie; + return pThis->SetActualRead(nValue == TRUE); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::DecideFirmwareRegistrationCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->DecideFirmwareRegistration((XnBool)pStream->m_DepthRegistration.GetValue(), (XnProcessingType)pStream->m_RegistrationType.GetValue(), pStream->GetResolution()); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::DecidePixelSizeFactorCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnSensorDepthStream* pStream = (XnSensorDepthStream*)pCookie; + return pStream->DecidePixelSizeFactor(); +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::ReadAGCBinsFromFile(XnGeneralProperty* pSender, const XnChar* csINIFile, const XnChar* csSection) +{ + XnStatus nRetVal = XN_STATUS_OK; + + for (XnUInt32 nBin = 0; nBin < XN_DEPTH_STREAM_AGC_NUMBER_OF_BINS; ++nBin) + { + XnChar csKey[XN_INI_MAX_LEN]; + XnInt32 nValue; + + XnDepthAGCBin bin; + bin.nBin = (XnUInt16)nBin; + + XnBool bHasMin = FALSE; + XnBool bHasMax = FALSE; + + sprintf(csKey, "AGCBin%uMinDepth", nBin); + + nRetVal = xnOSReadIntFromINI(csINIFile, csSection, csKey, &nValue); + if (nRetVal == XN_STATUS_OK) + { + bin.nMin = (XnUInt16)nValue; + bHasMin = TRUE; + } + + sprintf(csKey, "AGCBin%uMaxDepth", nBin); + nRetVal = xnOSReadIntFromINI(csINIFile, csSection, csKey, &nValue); + if (nRetVal == XN_STATUS_OK) + { + bin.nMax = (XnUInt16)nValue; + bHasMax = TRUE; + } + + if (bHasMax && bHasMin) + { + nRetVal = pSender->SetValue(XN_PACK_GENERAL_BUFFER(bin)); + XN_IS_STATUS_OK(nRetVal); + } + else if (bHasMin || bHasMax) + { + // we have only one + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Bin %d should have both min and max values!", nBin); + } + } + + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensorDepthStream::GetPixelRegistrationCallback(const XnGeneralProperty* /*pSender*/, const OniGeneralBuffer& gbValue, void* pCookie) +{ + XnSensorDepthStream* pThis = (XnSensorDepthStream*)pCookie; + + if (gbValue.dataSize != sizeof(XnPixelRegistration)) + { + return XN_STATUS_DEVICE_PROPERTY_SIZE_DONT_MATCH; + } + + XnPixelRegistration* pArgs = (XnPixelRegistration*)gbValue.data; + + return pThis->GetImageCoordinatesOfDepthPixel(pArgs->nDepthX, pArgs->nDepthY, pArgs->nDepthValue, pArgs->nImageXRes, pArgs->nImageYRes, pArgs->nImageX, pArgs->nImageY); +} + diff --git a/Source/Drivers/PS1080/Sensor/XnSensorDepthStream.h b/Source/Drivers/PS1080/Sensor/XnSensorDepthStream.h new file mode 100644 index 0000000..3f773d3 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorDepthStream.h @@ -0,0 +1,192 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_DEPTH_STREAM_H__ +#define __XN_SENSOR_DEPTH_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnDeviceSensorProtocol.h" +#include "XnSensorStreamHelper.h" +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#if (XN_PLATFORM == XN_PLATFORM_LINUX_ARM || XN_PLATFORM == XN_PLATFORM_ANDROID_ARM) + #define XN_DEPTH_STREAM_DEFAULT_INPUT_FORMAT XN_IO_DEPTH_FORMAT_UNCOMPRESSED_12_BIT + #define XN_DEPTH_STREAM_DEFAULT_RESOLUTION XN_RESOLUTION_QQVGA +#else + #define XN_DEPTH_STREAM_DEFAULT_INPUT_FORMAT XN_IO_DEPTH_FORMAT_UNCOMPRESSED_11_BIT + #define XN_DEPTH_STREAM_DEFAULT_RESOLUTION XN_RESOLUTION_QVGA +#endif + +#define XN_DEPTH_STREAM_DEFAULT_FPS 30 +#define XN_DEPTH_STREAM_DEFAULT_OUTPUT_FORMAT ONI_PIXEL_FORMAT_DEPTH_1_MM +#define XN_DEPTH_STREAM_DEFAULT_REGISTRATION FALSE +#define XN_DEPTH_STREAM_DEFAULT_REGISTRATION_TYPE XN_PROCESSING_DONT_CARE +#define XN_DEPTH_STREAM_DEFAULT_HOLE_FILLER TRUE +#define XN_DEPTH_STREAM_DEFAULT_WHITE_BALANCE TRUE +#define XN_DEPTH_STREAM_DEFAULT_GAIN_OLD 50 +#define XN_DEPTH_STREAM_DEFAULT_GMC_MODE TRUE +#define XN_DEPTH_STREAM_DEFAULT_CLOSE_RANGE FALSE +#define XN_DEPTH_STREAM_DEFAULT_SHIFT_MAP_APPENDED TRUE + +#define XN_DEPTH_STREAM_DEFAULT_GMC_DEBUG FALSE +#define XN_DEPTH_STREAM_DEFAULT_WAVELENGTH_CORRECTION FALSE +#define XN_DEPTH_STREAM_DEFAULT_WAVELENGTH_CORRECTION_DEBUG FALSE + +//--------------------------------------------------------------------------- +// XnSensorDepthStream class +//--------------------------------------------------------------------------- +class XnSensorDepthStream : public XnDepthStream, public IXnSensorStream +{ +public: + XnSensorDepthStream(const XnChar* strName, XnSensorObjects* pObjects); + ~XnSensorDepthStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + XnStatus Free(); + XnStatus BatchConfig(const XnActualPropertiesHash& props) { return m_Helper.BatchConfig(props); } + + inline XnSensorStreamHelper* GetHelper() { return &m_Helper; } + + friend class XnDepthProcessor; + friend class XnOniDepthStream; + +protected: + inline XnSensorFirmwareParams* GetFirmwareParams() const { return m_Helper.GetFirmware()->GetParams(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Open() { return m_Helper.Open(); } + XnStatus Close() { return m_Helper.Close(); } + XnStatus CropImpl(OniFrame* pFrame, const OniCropping* pCropping); + XnStatus Mirror(OniFrame* pFrame) const; + XnStatus ConfigureStreamImpl(); + XnStatus OpenStreamImpl(); + XnStatus CloseStreamImpl(); + XnStatus CreateDataProcessor(XnDataProcessor** ppProcessor); + XnStatus MapPropertiesToFirmware(); + void GetFirmwareStreamConfig(XnResolutions* pnRes, XnUInt32* pnFPS) { *pnRes = GetResolution(); *pnFPS = GetFPS(); } + + XnStatus ApplyRegistration(OniDepthPixel* pDetphmap); + OniStatus GetSensorCalibrationInfo(void* data, int* dataSize); + XnStatus PopulateSensorCalibrationInfo(); + +protected: + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + XnStatus SetOutputFormat(OniPixelFormat nOutputFormat); + XnStatus SetMirror(XnBool bIsMirrored); + XnStatus SetResolution(XnResolutions nResolution); + XnStatus SetFPS(XnUInt32 nFPS); + virtual XnStatus SetInputFormat(XnIODepthFormats nInputFormat); + virtual XnStatus SetRegistration(XnBool bRegistration); + virtual XnStatus SetHoleFilter(XnBool bHoleFilter); + virtual XnStatus SetWhiteBalance(XnBool bWhiteBalance); + virtual XnStatus SetGain(XnUInt32 nGain); + virtual XnStatus SetRegistrationType(XnProcessingType type); + virtual XnStatus SetAGCBin(const XnDepthAGCBin* pBin); + virtual XnStatus GetAGCBin(XnDepthAGCBin* pBin); + XnStatus SetCropping(const OniCropping* pCropping); + XnStatus SetActualRead(XnBool bRead); + virtual XnStatus SetGMCMode(XnBool bGMCMode); + virtual XnStatus SetCloseRange(XnBool bCloseRange); + virtual XnStatus SetCroppingMode(XnCroppingMode mode); + XnStatus GetImageCoordinatesOfDepthPixel(XnUInt32 x, XnUInt32 y, OniDepthPixel z, XnUInt32 imageXRes, XnUInt32 imageYRes, XnUInt32& imageX, XnUInt32& imageY); + virtual XnStatus SetGMCDebug(XnBool bGMCDebug); + virtual XnStatus SetWavelengthCorrection(XnBool bWavelengthCorrection); + virtual XnStatus SetWavelengthCorrectionDebug(XnBool bWavelengthCorrectionDebug); + +private: + XnUInt32 CalculateExpectedSize(); + XnStatus DecideFirmwareRegistration(XnBool bRegistration, XnProcessingType registrationType, XnResolutions nRes); + XnStatus DecidePixelSizeFactor(); + XnStatus SetCroppingImpl(const OniCropping* pCropping, XnCroppingMode mode); + XnStatus CloseRangeControl(XnBool bEnabled); + + static XnStatus XN_CALLBACK_TYPE SetInputFormatCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetRegistrationCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetHoleFilterCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetWhiteBalanceCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetGainCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetRegistrationTypeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetAGCBinCallback(XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetAGCBinCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetActualReadCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE DecideFirmwareRegistrationCallback(const XnProperty* pSender, void* pCookie); + static XnStatus XN_CALLBACK_TYPE DecidePixelSizeFactorCallback(const XnProperty* pSender, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ReadAGCBinsFromFile(XnGeneralProperty* pSender, const XnChar* csINIFile, const XnChar* csSection); + static XnStatus XN_CALLBACK_TYPE SetGMCModeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetCloseRangeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetCroppingModeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE GetPixelRegistrationCallback(const XnGeneralProperty* pSender, const OniGeneralBuffer& gbValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetGMCDebugCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetWavelengthCorrectionCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetWavelengthCorrectionDebugCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnSensorStreamHelper m_Helper; + XnActualIntProperty m_InputFormat; + XnActualIntProperty m_DepthRegistration; + XnActualIntProperty m_HoleFilter; + XnActualIntProperty m_WhiteBalance; + XnActualIntProperty m_Gain; + XnActualIntProperty m_RegistrationType; + XnActualIntProperty m_CroppingMode; + XnGeneralProperty m_AGCBin; + + XnActualIntProperty m_FirmwareMirror; + XnActualIntProperty m_FirmwareRegistration; + + XnActualIntProperty m_FirmwareCropSizeX; + XnActualIntProperty m_FirmwareCropSizeY; + XnActualIntProperty m_FirmwareCropOffsetX; + XnActualIntProperty m_FirmwareCropOffsetY; + XnActualIntProperty m_FirmwareCropMode; + + XnActualIntProperty m_ActualRead; + XnActualIntProperty m_GMCMode; + XnActualIntProperty m_CloseRange; + XnGeneralProperty m_PixelRegistration; + + XnActualRealProperty m_HorizontalFOV; + XnActualRealProperty m_VerticalFOV; + + XnActualIntProperty m_GMCDebug; + XnActualIntProperty m_WavelengthCorrection; + XnActualIntProperty m_WavelengthCorrectionDebug; + + DepthUtilsHandle m_depthUtilsHandle; + DepthUtilsSensorCalibrationInfo m_calibrationInfo; + XnCallbackHandle m_hReferenceSizeChangedCallback; +}; + +#endif //__XN_SENSOR_DEPTH_STREAM_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFPS.cpp b/Source/Drivers/PS1080/Sensor/XnSensorFPS.cpp new file mode 100644 index 0000000..379eefa --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFPS.cpp @@ -0,0 +1,79 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnSensorFPS.h" +#include "XnDeviceSensor.h" +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +/* The number of frames to average FPS over */ +#define XN_SENSOR_FPS_FRAME_COUNT 180 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnSensorFPS::XnSensorFPS() : + m_nLastPrint(0), + m_FramesDump(NULL) +{ + xnFPSInit(&m_depth, XN_SENSOR_FPS_FRAME_COUNT); + xnFPSInit(&m_color, XN_SENSOR_FPS_FRAME_COUNT); + xnFPSInit(&m_ir, XN_SENSOR_FPS_FRAME_COUNT); + + m_FramesDump = xnDumpFileOpen(XN_MASK_SENSOR_FPS, "FramesTimes.csv"); + xnDumpFileWriteString(m_FramesDump, "TS,Type,FrameID,FrameTS\n"); +} + +XnSensorFPS::~XnSensorFPS() +{ + xnFPSFree(&m_depth); + xnFPSFree(&m_color); + xnFPSFree(&m_ir); + + xnDumpFileClose(m_FramesDump); +} + +void XnSensorFPS::Mark(XnFPSData* pFPS, const XnChar* csName, XnUInt32 nFrameID, XnUInt64 nTS) +{ + if (!xnLogIsEnabled(XN_MASK_SENSOR_FPS, XN_LOG_VERBOSE)) + return; + + XnUInt64 nNow; + xnOSGetHighResTimeStamp(&nNow); + + xnFPSMarkFrame(pFPS, nNow); + + xnDumpFileWriteString(m_FramesDump, "%llu,%s,%u,%llu\n", nNow, csName, nFrameID, nTS); + + // get current time in seconds + nNow /= 1000000; + + if (nNow != m_nLastPrint) + { + m_nLastPrint = nNow; + xnLogVerbose(XN_MASK_SENSOR_FPS, "[FPS] Frames - C: %5.2f, D: %5.2f, I: %5.2f", + xnFPSCalc(&m_color), xnFPSCalc(&m_depth), xnFPSCalc(&m_ir)); + } +} diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFPS.h b/Source/Drivers/PS1080/Sensor/XnSensorFPS.h new file mode 100644 index 0000000..9afef6a --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFPS.h @@ -0,0 +1,58 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_FPS_H__ +#define __XN_SENSOR_FPS_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_MASK_SENSOR_FPS "FramesTimes" + +//--------------------------------------------------------------------------- +// XnSensorFPS class +//--------------------------------------------------------------------------- +class XnSensorFPS +{ +public: + XnSensorFPS(); + ~XnSensorFPS(); + + inline void MarkDepth(XnUInt32 nFrameID, XnUInt64 nTS) { Mark(&m_depth, "DepthInput", nFrameID, nTS); } + inline void MarkColor(XnUInt32 nFrameID, XnUInt64 nTS) { Mark(&m_color, "ImageInput", nFrameID, nTS); } + inline void MarkIr(XnUInt32 nFrameID, XnUInt64 nTS) {Mark(&m_ir, "IrInput", nFrameID, nTS);} +private: + void Mark(XnFPSData* pFPS, const XnChar* csName, XnUInt32 nFrameID, XnUInt64 nTS); + + XnFPSData m_depth; + XnFPSData m_color; + XnFPSData m_ir; + + XnUInt64 m_nLastPrint; + XnDumpFile* m_FramesDump; +}; + +#endif //__XN_SENSOR_FPS_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFirmware.cpp b/Source/Drivers/PS1080/Sensor/XnSensorFirmware.cpp new file mode 100644 index 0000000..4e6b2a8 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFirmware.cpp @@ -0,0 +1,152 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnSensorFirmware.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnSensorFirmware::XnSensorFirmware(XnDevicePrivateData* pDevicePrivateData) : + m_pInfo(&pDevicePrivateData->FWInfo), + m_Commands(pDevicePrivateData), + m_Params(m_pInfo, &m_Commands), + m_Streams(pDevicePrivateData), + m_FixedParams(pDevicePrivateData), + m_pDevicePrivateData(pDevicePrivateData) +{ +} + +XnStatus XnSensorFirmware::Init(XnBool bReset, XnBool bLeanInit) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // check current mode + XnUInt16 nMode; + nRetVal = XnHostProtocolGetMode(m_pDevicePrivateData, nMode); + XN_IS_STATUS_OK(nRetVal); + + if (bReset) + { + // check if safe mode + if (nMode == XN_HOST_PROTOCOL_MODE_SAFE_MODE) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_SAFE_MODE, XN_MASK_DEVICE_SENSOR, "Device is in safe mode. Cannot start any stream!"); + } + + // check if device is alive + XnUInt32 nCounter = 5; + while (nCounter) + { + nRetVal = XnHostProtocolKeepAlive(m_pDevicePrivateData); + if (nRetVal != XN_STATUS_OK) + { + nCounter--; + } + else + nCounter = 0; + } + if (nRetVal != XN_STATUS_OK) + { + printf("Keep alive failed!\n"); + return nRetVal; + } + + // perform a soft reset (to start clean) + nRetVal = XnHostProtocolReset(m_pDevicePrivateData, XN_RESET_TYPE_SOFT_FIRST); + if (nRetVal != XN_STATUS_OK) + { + printf("Couldn't reset the device!\n"); + return nRetVal; + } + + // wait for sensor to recover from reset + xnOSSleep(m_pDevicePrivateData->FWInfo.nUSBDelaySoftReset); + + // send keep alive again to see sensor is up + nCounter = 10; + while (nCounter) + { + nRetVal = XnHostProtocolKeepAlive(m_pDevicePrivateData); + if (nRetVal != XN_STATUS_OK) + { + nCounter--; + xnOSSleep(10); + } + else + break; + } + if (nCounter == 0) + { + printf("10 keep alives is too much - stopping\n"); + return nRetVal; + } + + nRetVal = XnHostProtocolGetMode(m_pDevicePrivateData, nMode); + XN_IS_STATUS_OK(nRetVal); + + // check if safe mode + if (nMode == XN_HOST_PROTOCOL_MODE_SAFE_MODE) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_SAFE_MODE, XN_MASK_DEVICE_SENSOR, "Device is in safe mode. Cannot start any stream!"); + } + } + + if (!bLeanInit) + { + nRetVal = m_FixedParams.Init(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Params.Init(); + XN_IS_STATUS_OK(nRetVal); + + if (nMode == XN_HOST_PROTOCOL_MODE_PS) + { + nRetVal = m_Params.UpdateAllProperties(); + XN_IS_STATUS_OK(nRetVal); + } + + // Check if image is supported + if (m_pInfo->bGetImageCmosTypeSupported) + { + m_pInfo->bImageSupported = (m_FixedParams.GetImageCmosType() != 0); + } + else + { + // This is an ugly patch to find out if this sensor has an image CMOS. It will be fixed + // in future firmwares so we can just ask. + XnUInt16 nLines; + nRetVal = XnHostProtocolGetCmosBlanking(m_pDevicePrivateData, XN_CMOS_TYPE_IMAGE, &nLines); + m_pInfo->bImageSupported = (nRetVal == XN_STATUS_OK && nLines > 0); + } + + nRetVal = m_Streams.Init(); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +void XnSensorFirmware::Free() +{ + m_Params.Free(); +} \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFirmware.h b/Source/Drivers/PS1080/Sensor/XnSensorFirmware.h new file mode 100644 index 0000000..e5a79d8 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFirmware.h @@ -0,0 +1,59 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_FIRMWARE_H__ +#define __XN_SENSOR_FIRMWARE_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnFirmwareInfo.h" +#include "XnFirmwareCommands.h" +#include "XnSensorFirmwareParams.h" +#include "XnFirmwareStreams.h" +#include "XnSensorFixedParams.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnSensorFirmware +{ +public: + XnSensorFirmware(XnDevicePrivateData* pDevicePrivateData); + XnStatus Init(XnBool bReset, XnBool bLeanInit); + void Free(); + + inline XnFirmwareInfo* GetInfo() { return m_pInfo; } + inline const XnFirmwareInfo* GetInfo() const { return m_pInfo; } + inline XnFirmwareCommands* GetCommands() { return &m_Commands; } + inline XnSensorFirmwareParams* GetParams() { return &m_Params; } + inline XnFirmwareStreams* GetStreams() { return &m_Streams; } + inline XnSensorFixedParams* GetFixedParams() { return &m_FixedParams; } + +private: + XnFirmwareInfo* m_pInfo; + XnFirmwareCommands m_Commands; + XnSensorFirmwareParams m_Params; + XnFirmwareStreams m_Streams; + XnSensorFixedParams m_FixedParams; + XnDevicePrivateData* m_pDevicePrivateData; +}; + +#endif //__XN_SENSOR_FIRMWARE_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.cpp b/Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.cpp new file mode 100644 index 0000000..f93f333 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.cpp @@ -0,0 +1,655 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnSensorFirmwareParams.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnSensorFirmwareParams::XnSensorFirmwareParams(XnFirmwareInfo* pInfo, XnFirmwareCommands* pCommands) : + /* Member Name Firmware Param Min Valid Version Max Valid Version Value if wrong version */ + /* ==================== ======================== ===================================== ==================== ==================== ====================== */ + m_FrameSyncEnabled(0, "FrameSync"), + m_RegistrationEnabled(0, "Registration"), + m_Stream0Mode(0, "Stream0Mode"), + m_Stream1Mode(0, "Stream1Mode"), + m_Stream2Mode(0, "Stream2Mode"), + m_AudioStereo(0, "AudioStereo"), + m_AudioSampleRate(0, "AudioSampleRate"), + m_AudioLeftChannelGain(0, "AudioLeftChannelGain"), + m_AudioRightChannelGain(0, "AudioRightChannelGain"), + m_ImageFormat(0, "ImageFormat"), + m_ImageResolution(0, "ImageResolution"), + m_ImageFPS(0, "ImageFPS"), + m_ImageQuality(0, "ImageQuality"), + m_ImageFlickerDetection(0, "ImageFlicker"), + m_ImageCropSizeX(0, "ImageCropSizeX"), + m_ImageCropSizeY(0, "ImageCropSizeY"), + m_ImageCropOffsetX(0, "ImageCropOffsetX"), + m_ImageCropOffsetY(0, "ImageCropOffsetY"), + m_ImageCropMode(0, "ImageCropEnabled"), + m_DepthFormat(0, "DepthFormat"), + m_DepthResolution(0, "DepthResolution"), + m_DepthFPS(0, "DepthFPS"), + m_DepthGain(0, "DepthGain"), + m_DepthHoleFilter(0, "DepthHoleFilter"), + m_DepthMirror(0, "DepthMirror"), + m_DepthDecimation(0, "DepthDecimation"), + m_DepthCropSizeX(0, "DepthCropSizeX"), + m_DepthCropSizeY(0, "DepthCropSizeY"), + m_DepthCropOffsetX(0, "DepthCropOffsetX"), + m_DepthCropOffsetY(0, "DepthCropOffsetY"), + m_DepthCropMode(0, "DepthCropEnabled"), + m_DepthWhiteBalance(0, "DepthWhiteBalance"), + m_IRFormat(0, "IRFormat"), + m_IRResolution(0, "IRResolution"), + m_IRFPS(0, "IRFPS"), + m_IRCropSizeX(0, "IRCropSizeX"), + m_IRCropSizeY(0, "IRCropSizeY"), + m_IRCropOffsetX(0, "IRCropOffsetX"), + m_IRCropOffsetY(0, "IRCropOffsetY"), + m_IRCropMode(0, "IRCropEnabled"), + m_ImageMirror(0, "ImageMirror"), + m_IRMirror(0, "IRMirror"), + m_ReferenceResolution(0, "ReferenceResolution", 0, "Firmware"), + m_GMCMode(0, "GMCMode"), + m_ImageSharpness(0, "ImageSharpness"), + m_ImageAutoWhiteBalance(0, "ImageAutoWhiteBalance"), + m_ImageColorTemperature(0, "ImageColorTemperature"), + m_ImageBacklightCompensation(0, "ImageBacklightCompensation"), + m_ImageAutoExposure(0, "ImageAutoExposure"), + m_ImageExposureBar(0, "ImageExposureBar"), + m_ImageLowLightCompensation(0, "ImageLowLightCompensation"), + m_ImageGain(0, "ImageGain"), + m_DepthCloseRange(0, "CloseRange"), + m_LogFilter(0, "LogFilter"), + m_GMCDebug(0, "GMCDebug"), + m_APCEnabled(0, "APCEnabled"), + m_WavelengthCorrection(0, "WavelengthCorrection"), + m_WavelengthCorrectionDebug(0, "WavelengthCorrectionDebug"), + m_AllFirmwareParams(), + m_pInfo(pInfo), + m_pCommands(pCommands), + m_bInTransaction(FALSE) +{ + m_ReferenceResolution.SetLogSeverity(XN_LOG_VERBOSE); +} + +XnSensorFirmwareParams::~XnSensorFirmwareParams() +{ +} + +XnStatus XnSensorFirmwareParams::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + /* Property Param MinVersion MaxVersion ValueIfNotSupported */ + /* ====================== ======================================= ==================== ==================== =================== */ + nRetVal = AddFirmwareParam( m_FrameSyncEnabled, PARAM_GENERAL_FRAME_SYNC); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_RegistrationEnabled, PARAM_GENERAL_REGISTRATION_ENABLE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_Stream0Mode, PARAM_GENERAL_STREAM0_MODE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_Stream1Mode, PARAM_GENERAL_STREAM1_MODE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareAudioParam(m_Stream2Mode, PARAM_GENERAL_STREAM2_MODE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareAudioParam(m_AudioStereo, PARAM_AUDIO_STEREO_MODE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareAudioParam(m_AudioSampleRate, PARAM_AUDIO_SAMPLE_RATE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareAudioParam(m_AudioLeftChannelGain, PARAM_AUDIO_LEFT_CHANNEL_VOLUME_LEVEL); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareAudioParam(m_AudioRightChannelGain, PARAM_AUDIO_RIGHT_CHANNEL_VOLUME_LEVEL); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageFormat, PARAM_IMAGE_FORMAT); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageResolution, PARAM_IMAGE_RESOLUTION); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageFPS, PARAM_IMAGE_FPS); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageQuality, PARAM_IMAGE_QUALITY); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageFlickerDetection, PARAM_IMAGE_FLICKER_DETECTION); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageCropSizeX, PARAM_IMAGE_CROP_SIZE_X, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageCropSizeY, PARAM_IMAGE_CROP_SIZE_Y, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageCropOffsetX, PARAM_IMAGE_CROP_OFFSET_X, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageCropOffsetY, PARAM_IMAGE_CROP_OFFSET_Y, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageCropMode, PARAM_IMAGE_CROP_MODE, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthFormat, PARAM_DEPTH_FORMAT); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthResolution, PARAM_DEPTH_RESOLUTION); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthFPS, PARAM_DEPTH_FPS); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthGain, PARAM_DEPTH_AGC); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthHoleFilter, PARAM_DEPTH_HOLE_FILTER); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthMirror, PARAM_DEPTH_MIRROR, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthDecimation, PARAM_DEPTH_DECIMATION); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthCropSizeX, PARAM_DEPTH_CROP_SIZE_X, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthCropSizeY, PARAM_DEPTH_CROP_SIZE_Y, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthCropOffsetX, PARAM_DEPTH_CROP_OFFSET_X, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthCropOffsetY, PARAM_DEPTH_CROP_OFFSET_Y, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthCropMode, PARAM_DEPTH_CROP_MODE, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRFormat, PARAM_IR_FORMAT); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRResolution, PARAM_IR_RESOLUTION); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRFPS, PARAM_IR_FPS); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRCropSizeX, PARAM_IR_CROP_SIZE_X, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRCropSizeY, PARAM_IR_CROP_SIZE_Y, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRCropOffsetX, PARAM_IR_CROP_OFFSET_X, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRCropOffsetY, PARAM_IR_CROP_OFFSET_Y, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRCropMode, PARAM_IR_CROP_MODE, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthWhiteBalance, PARAM_DEPTH_WHITE_BALANCE_ENABLE, XN_SENSOR_FW_VER_4_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageMirror, PARAM_IMAGE_MIRROR, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_IRMirror, PARAM_IR_MIRROR, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_GMCMode, PARAM_DEPTH_GMC_MODE, XN_SENSOR_FW_VER_3_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageSharpness, PARAM_IMAGE_SHARPNESS, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, 50); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageAutoWhiteBalance, PARAM_IMAGE_AUTO_WHITE_BALANCE_MODE, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageColorTemperature, PARAM_IMAGE_COLOR_TEMPERATURE, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageBacklightCompensation,PARAM_IMAGE_BACK_LIGHT_COMPENSATION, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageAutoExposure, PARAM_IMAGE_AUTO_EXPOSURE_MODE, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageExposureBar, PARAM_IMAGE_EXPOSURE_BAR, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageLowLightCompensation,PARAM_IMAGE_LOW_LIGHT_COMPENSATION_MODE, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_ImageGain, PARAM_IMAGE_AGC, XN_SENSOR_FW_VER_5_4, XN_SENSOR_FW_VER_UNKNOWN, 0); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_DepthCloseRange, PARAM_DEPTH_CLOSE_RANGE, XN_SENSOR_FW_VER_5_6, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_LogFilter, PARAM_MISC_LOG_FILTER); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_GMCDebug, PARAM_GMC_DEBUG, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_APCEnabled, PARAM_APC_ENABLE, XN_SENSOR_FW_VER_5_0, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_WavelengthCorrection, PARAM_WAVELENGTH_CORRECTION_ENABLED, XN_SENSOR_FW_VER_5_2, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + nRetVal = AddFirmwareParam( m_WavelengthCorrectionDebug,PARAM_WAVELENGTH_CORRECTION_DEBUG_ENABLED, XN_SENSOR_FW_VER_5_2, XN_SENSOR_FW_VER_UNKNOWN, FALSE); + XN_IS_STATUS_OK(nRetVal); + + // override some props + m_ImageFormat.UpdateSetCallback(SetImageFormatCallback, this); + + // register for some interesting changes + XnCallbackHandle hCallbackDummy; + nRetVal = m_Stream0Mode.OnChangeEvent().Register(ReferenceResolutionPropertyValueChanged, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Stream1Mode.OnChangeEvent().Register(ReferenceResolutionPropertyValueChanged, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_IRResolution.OnChangeEvent().Register(ReferenceResolutionPropertyValueChanged, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_DepthFPS.OnChangeEvent().Register(ReferenceResolutionPropertyValueChanged, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = RecalculateReferenceResolution(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnSensorFirmwareParams::Free() +{ + m_AllFirmwareParams.Clear(); +} + +XnStatus XnSensorFirmwareParams::AddFirmwareParam(XnActualIntProperty& Property, XnUInt16 nFirmwareParam, XnFWVer nMinVer /* = XN_SENSOR_FW_VER_UNKNOWN */, XnFWVer nMaxVer /* = XN_SENSOR_FW_VER_UNKNOWN */, XnUInt16 nValueIfNotSupported /* = 0 */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFirmwareParam param; + param.pProperty = &Property; + param.nFirmwareParam = nFirmwareParam; + param.MinVer = nMinVer; + param.MaxVer = nMaxVer; + param.nValueIfNotSupported = nValueIfNotSupported; + + nRetVal = m_AllFirmwareParams.Set(&Property, param); + XN_IS_STATUS_OK(nRetVal); + + XnChar csNewName[XN_DEVICE_MAX_STRING_LENGTH]; + sprintf(csNewName, "%s (%d)", Property.GetName(), nFirmwareParam); + + Property.UpdateName("Firmware", csNewName); + Property.SetLogSeverity(XN_LOG_VERBOSE); + Property.SetAlwaysSet(TRUE); + Property.UpdateSetCallback(SetFirmwareParamCallback, this); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::AddFirmwareAudioParam(XnActualIntProperty& Property, XnUInt16 nFirmwareParam, XnFWVer nMinVer /* = XN_SENSOR_FW_VER_3_0 */, XnFWVer nMaxVer /* = XN_SENSOR_FW_VER_UNKNOWN */, XnUInt16 nValueIfNotSupported /* = 0 */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = AddFirmwareParam(Property, nFirmwareParam, nMinVer, nMaxVer, nValueIfNotSupported); + XN_IS_STATUS_OK(nRetVal); + + Property.UpdateSetCallback(SetFirmwareAudioParamCallback, this); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::UpdateAllProperties() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Reading all params from firmware..."); + + for (XnFirmwareParamsHash::Iterator it = m_AllFirmwareParams.Begin(); it != m_AllFirmwareParams.End(); ++it) + { + XnFirmwareParam& param = it->Value(); + nRetVal = UpdateProperty(¶m); + XN_IS_STATUS_OK(nRetVal); + } + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Firmware params were updated."); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::StartTransaction() +{ + if (m_bInTransaction) + { + return XN_STATUS_ERROR; + } + + m_bInTransaction = TRUE; + m_Transaction.Clear(); + m_TransactionOrder.Clear(); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::CommitTransaction() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bInTransaction) + { + return XN_STATUS_ERROR; + } + + // we are no longer in transaction, even if we fail to commit. + m_bInTransaction = FALSE; + + for (XnActualIntPropertyList::Iterator it = m_TransactionOrder.Begin(); it != m_TransactionOrder.End(); ++it) + { + XnActualIntProperty* pProp = *it; + + XnUInt32 nValue; + nRetVal = m_Transaction.Get(pProp, nValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SetFirmwareParamImpl(pProp, nValue); + XN_IS_STATUS_OK(nRetVal); + } + + m_Transaction.Clear(); + m_TransactionOrder.Clear(); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::CommitTransactionAsBatch() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bInTransaction) + { + return XN_STATUS_ERROR; + } + + // we are no longer in transaction, even if we fail to commit. + m_bInTransaction = FALSE; + + if (m_TransactionOrder.Size() != 0) + { + XnUInt32 nMaxCount = m_TransactionOrder.Size(); + XnInnerParamData* pParams; + XN_VALIDATE_CALLOC(pParams, XnInnerParamData, nMaxCount); + + XnChar strLogMessage[1024]; + XnUInt32 nMaxLength = 1024; + XnUInt32 nLength = 0; + XnUInt32 nChars; + xnOSStrFormat(strLogMessage + nLength, nMaxLength - nLength, &nChars, "Setting firmware params:\n\t"); + nLength += nChars; + + XnUInt32 nCount = 0; + + for (XnActualIntPropertyList::Iterator it = m_TransactionOrder.Begin(); it != m_TransactionOrder.End(); ++it) + { + XnActualIntProperty* pProp = *it; + + XnUInt32 nValue; + nRetVal = m_Transaction.Get(pProp, nValue); + if (nRetVal != XN_STATUS_OK) + { + xnOSFree(pParams); + return (nRetVal); + } + + XnFirmwareParam* pParam; + nRetVal = CheckFirmwareParam(pProp, nValue, &pParam); + if (nRetVal != XN_STATUS_OK) + { + xnOSFree(pParams); + return (nRetVal); + } + + if (pParam != NULL) + { + xnOSStrFormat(strLogMessage + nLength, nMaxLength - nLength, &nChars, "%s = %u\n\t", pProp->GetName(), nValue); + nLength += nChars; + + pParams[nCount].nParam = pParam->nFirmwareParam; + pParams[nCount].nValue = (XnUInt16)nValue; + nCount++; + } + } + + xnLogVerbose(XN_MASK_SENSOR_PROTOCOL, "%s", strLogMessage); + + // set all params + nRetVal = m_pCommands->SetMultipleFirmwareParams(pParams, nCount); + xnOSFree(pParams); + XN_IS_STATUS_OK(nRetVal); + + // and update their props + for (XnActualIntPropertyList::Iterator it = m_TransactionOrder.Begin(); it != m_TransactionOrder.End(); ++it) + { + XnActualIntProperty* pProp = *it; + + XnUInt32 nValue; + nRetVal = m_Transaction.Get(pProp, nValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pProp->UnsafeUpdateValue(nValue); + XN_IS_STATUS_OK(nRetVal); + } + } + + m_Transaction.Clear(); + m_TransactionOrder.Clear(); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::RollbackTransaction() +{ + if (!m_bInTransaction) + { + return XN_STATUS_ERROR; + } + + m_Transaction.Clear(); + m_TransactionOrder.Clear(); + m_bInTransaction = FALSE; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::UpdateProperty(XnFirmwareParam* pParam) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt16 nNewValue; + + // check version + if ((pParam->MinVer != XN_SENSOR_FW_VER_UNKNOWN && m_pInfo->nFWVer < pParam->MinVer) || + (pParam->MaxVer != XN_SENSOR_FW_VER_UNKNOWN && m_pInfo->nFWVer > pParam->MaxVer)) + { + // version not supported + nNewValue = pParam->nValueIfNotSupported; + } + else + { + // Read value from firmware + nRetVal = m_pCommands->GetFirmwareParam(pParam->nFirmwareParam, &nNewValue); + XN_IS_STATUS_OK(nRetVal); + } + + // update value if needed + if (nNewValue != pParam->pProperty->GetValue()) + { + // update base (don't call our function, so that it won't update firmware) + nRetVal = pParam->pProperty->UnsafeUpdateValue(nNewValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::SetFirmwareParam(XnActualIntProperty* pProperty, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_bInTransaction) + { + nRetVal = m_Transaction.Set(pProperty, (XnUInt32)nValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_TransactionOrder.AddLast(pProperty); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = SetFirmwareParamImpl(pProperty, nValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::SetFirmwareAudioParam(XnActualIntProperty* pProperty, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // check if audio is not supported, and trying to change the value + if (!m_pInfo->bAudioSupported && nValue != pProperty->GetValue()) + { + return (XN_STATUS_DEVICE_UNSUPPORTED_PARAMETER); + } + + nRetVal = SetFirmwareParam(pProperty, nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::SetImageFormat(XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + +/* + if (nValue == XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER) + { + nValue = XN_IO_IMAGE_FORMAT_BAYER; + } +*/ + + nRetVal = SetFirmwareParam(&m_ImageFormat, nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::CheckFirmwareParam(XnActualIntProperty* pProperty, XnUInt64 nValue, XnFirmwareParam** ppParam) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // find the property in the hash + XnFirmwareParam* pParam; + nRetVal = m_AllFirmwareParams.Get(pProperty, pParam); + XN_IS_STATUS_OK(nRetVal); + + *ppParam = NULL; + + // check version + if ((pParam->MinVer != XN_SENSOR_FW_VER_UNKNOWN && m_pInfo->nFWVer < pParam->MinVer) || + (pParam->MaxVer != XN_SENSOR_FW_VER_UNKNOWN && m_pInfo->nFWVer > pParam->MaxVer)) + { + // we only raise an error when trying to change the value... + if (nValue != pParam->nValueIfNotSupported) + { + return (XN_STATUS_DEVICE_UNSUPPORTED_PARAMETER); + } + } + else + { + *ppParam = pParam; + } + + return XN_STATUS_OK; +} + +XnStatus XnSensorFirmwareParams::SetFirmwareParamImpl(XnActualIntProperty* pProperty, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFirmwareParam* pParam; + nRetVal = CheckFirmwareParam(pProperty, nValue, &pParam); + XN_IS_STATUS_OK(nRetVal); + + if (pParam != NULL) + { + // update firmware + nRetVal = m_pCommands->SetFirmwareParam(pParam->nFirmwareParam, (XnUInt16)nValue); + XN_IS_STATUS_OK(nRetVal); + + // update property + nRetVal = pParam->pProperty->UnsafeUpdateValue(nValue); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::SetStreamMode(XnActualIntProperty* pProperty, XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // we require that every change to mode will go through OFF + if (nValue != XN_VIDEO_STREAM_OFF && pProperty->GetValue() != XN_VIDEO_STREAM_OFF) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Firmware stream is already in use!"); + } + + // OK, set it + nRetVal = SetFirmwareParam(pProperty, nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorFirmwareParams::RecalculateReferenceResolution() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // by default, the 1.3 MP reference is used + XnResolutions nRes = XN_RESOLUTION_SXGA; + + // only in the following cases, VGA reference is used: + // 1. Depth is running in 60 FPS + // 2. IR stream is running in QVGA + if ((m_Stream1Mode.GetValue() == XN_VIDEO_STREAM_DEPTH && m_DepthFPS.GetValue() == 60) || + (m_Stream0Mode.GetValue() == XN_VIDEO_STREAM_IR && m_IRResolution.GetValue() == XN_RESOLUTION_QVGA)) + { + nRes = XN_RESOLUTION_VGA; + } + + if (nRes != (XnResolutions)m_ReferenceResolution.GetValue()) + { + nRetVal = m_ReferenceResolution.UnsafeUpdateValue(nRes); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XN_CALLBACK_TYPE XnSensorFirmwareParams::SetFirmwareParamCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie) +{ + XnSensorFirmwareParams* pThis = (XnSensorFirmwareParams*)pCookie; + return pThis->SetFirmwareParam(pSender, nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorFirmwareParams::SetFirmwareAudioParamCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie) +{ + XnSensorFirmwareParams* pThis = (XnSensorFirmwareParams*)pCookie; + return pThis->SetFirmwareAudioParam(pSender, nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorFirmwareParams::SetImageFormatCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorFirmwareParams* pThis = (XnSensorFirmwareParams*)pCookie; + return pThis->SetImageFormat(nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorFirmwareParams::SetStreamModeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie) +{ + XnSensorFirmwareParams* pThis = (XnSensorFirmwareParams*)pCookie; + return pThis->SetStreamMode(pSender, nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorFirmwareParams::ReferenceResolutionPropertyValueChanged(const XnProperty* /*pSender*/, void* pCookie) +{ + XnSensorFirmwareParams* pThis = (XnSensorFirmwareParams*)pCookie; + return pThis->RecalculateReferenceResolution(); +} diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.h b/Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.h new file mode 100644 index 0000000..6c10a5c --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFirmwareParams.h @@ -0,0 +1,161 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_FIRMWARE_PARAMS_H__ +#define __XN_SENSOR_FIRMWARE_PARAMS_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnParams.h" +#include +#include "XnFirmwareInfo.h" +#include "XnFirmwareCommands.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +/** +* Holds all firmware params +*/ +class XnSensorFirmwareParams +{ +public: + XnSensorFirmwareParams(XnFirmwareInfo* pInfo, XnFirmwareCommands* pCommands); + ~XnSensorFirmwareParams(); + + //--------------------------------------------------------------------------- + // Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + void Free(); + XnStatus UpdateAllProperties(); + XnStatus StartTransaction(); + XnStatus CommitTransaction(); + XnStatus CommitTransactionAsBatch(); + XnStatus RollbackTransaction(); + + XnActualIntProperty m_FrameSyncEnabled; + XnActualIntProperty m_RegistrationEnabled; + XnActualIntProperty m_Stream0Mode; + XnActualIntProperty m_Stream1Mode; + XnActualIntProperty m_Stream2Mode; + XnActualIntProperty m_AudioStereo; + XnActualIntProperty m_AudioSampleRate; + XnActualIntProperty m_AudioLeftChannelGain; + XnActualIntProperty m_AudioRightChannelGain; + XnActualIntProperty m_ImageFormat; + XnActualIntProperty m_ImageResolution; + XnActualIntProperty m_ImageFPS; + XnActualIntProperty m_ImageQuality; + XnActualIntProperty m_ImageFlickerDetection; + XnActualIntProperty m_ImageCropSizeX; + XnActualIntProperty m_ImageCropSizeY; + XnActualIntProperty m_ImageCropOffsetX; + XnActualIntProperty m_ImageCropOffsetY; + XnActualIntProperty m_ImageCropMode; + XnActualIntProperty m_DepthFormat; + XnActualIntProperty m_DepthResolution; + XnActualIntProperty m_DepthFPS; + XnActualIntProperty m_DepthGain; + XnActualIntProperty m_DepthHoleFilter; + XnActualIntProperty m_DepthMirror; + XnActualIntProperty m_DepthDecimation; + XnActualIntProperty m_DepthCropSizeX; + XnActualIntProperty m_DepthCropSizeY; + XnActualIntProperty m_DepthCropOffsetX; + XnActualIntProperty m_DepthCropOffsetY; + XnActualIntProperty m_DepthCropMode; + XnActualIntProperty m_DepthWhiteBalance; + XnActualIntProperty m_IRFormat; + XnActualIntProperty m_IRResolution; + XnActualIntProperty m_IRFPS; + XnActualIntProperty m_IRCropSizeX; + XnActualIntProperty m_IRCropSizeY; + XnActualIntProperty m_IRCropOffsetX; + XnActualIntProperty m_IRCropOffsetY; + XnActualIntProperty m_IRCropMode; + XnActualIntProperty m_ImageMirror; + XnActualIntProperty m_IRMirror; + XnActualIntProperty m_ReferenceResolution; + XnActualIntProperty m_GMCMode; + XnActualIntProperty m_ImageSharpness; + XnActualIntProperty m_ImageAutoWhiteBalance; + XnActualIntProperty m_ImageColorTemperature; + XnActualIntProperty m_ImageBacklightCompensation; + XnActualIntProperty m_ImageAutoExposure; + XnActualIntProperty m_ImageExposureBar; + XnActualIntProperty m_ImageLowLightCompensation; + XnActualIntProperty m_ImageGain; + XnActualIntProperty m_DepthCloseRange; + XnActualIntProperty m_LogFilter; + XnActualIntProperty m_GMCDebug; + XnActualIntProperty m_APCEnabled; + XnActualIntProperty m_WavelengthCorrection; + XnActualIntProperty m_WavelengthCorrectionDebug; + +private: + typedef struct XnFirmwareParam + { + XnActualIntProperty* pProperty; + XnUInt16 nFirmwareParam; + XnFWVer MinVer; + XnFWVer MaxVer; + XnUInt16 nValueIfNotSupported; + } XnFirmwareParam; + + typedef xnl::Hash XnFirmwareParamsHash; + typedef xnl::List XnActualIntPropertyList; + typedef xnl::Hash XnPropertyToValueHash; + + XnStatus AddFirmwareParam(XnActualIntProperty& Property, XnUInt16 nFirmwareParam, XnFWVer nMinVer = XN_SENSOR_FW_VER_UNKNOWN, XnFWVer nMaxVer = XN_SENSOR_FW_VER_UNKNOWN, XnUInt16 nValueIfNotSupported = 0); + XnStatus AddFirmwareAudioParam(XnActualIntProperty& Property, XnUInt16 nFirmwareParam, XnFWVer nMinVer = XN_SENSOR_FW_VER_3_0, XnFWVer nMaxVer = XN_SENSOR_FW_VER_UNKNOWN, XnUInt16 nValueIfNotSupported = 0); + + XnStatus UpdateProperty(XnFirmwareParam* pParam); + + XnStatus SetFirmwareParam(XnActualIntProperty* pProperty, XnUInt64 nValue); + XnStatus SetFirmwareAudioParam(XnActualIntProperty* pProperty, XnUInt64 nValue); + XnStatus SetImageFormat(XnUInt64 nValue); + XnStatus SetStreamMode(XnActualIntProperty* pProperty, XnUInt64 nValue); + XnStatus RecalculateReferenceResolution(); + XnStatus GetFirmwareParam(XnActualIntProperty* pProperty, XnFirmwareParam** ppParam); + + XnStatus SetFirmwareParamImpl(XnActualIntProperty* pProperty, XnUInt64 nValue); + XnStatus CheckFirmwareParam(XnActualIntProperty* pProperty, XnUInt64 nValue, XnFirmwareParam** ppParam); + + static XnStatus XN_CALLBACK_TYPE SetFirmwareParamCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetFirmwareAudioParamCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetImageFormatCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetStreamModeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE ReferenceResolutionPropertyValueChanged(const XnProperty* pSender, void* pCookie); + + XnFirmwareParamsHash m_AllFirmwareParams; + + XnFirmwareInfo* m_pInfo; + XnFirmwareCommands* m_pCommands; + XnBool m_bInTransaction; + XnActualIntPropertyList m_TransactionOrder; // the transaction according to the order in which it was set + XnPropertyToValueHash m_Transaction; // maps a property to its new value +}; + +#endif //__XN_SENSOR_FIRMWARE_PARAMS_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFixedParams.cpp b/Source/Drivers/PS1080/Sensor/XnSensorFixedParams.cpp new file mode 100644 index 0000000..c9b63c1 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFixedParams.cpp @@ -0,0 +1,104 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnSensorFixedParams.h" +#include "XnHostProtocol.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnSensorFixedParams::XnSensorFixedParams(XnDevicePrivateData* pDevicePrivateData) : + m_pDevicePrivateData(pDevicePrivateData), + m_nSensorDepthCMOSI2CBus(0), + m_nSensorDepthCMOSI2CSlaveAddress(0), + m_nSensorImageCMOSI2CBus(0), + m_nSensorImageCMOSI2CSlaveAddress(0), + m_nZeroPlaneDistance(0), + m_dZeroPlanePixelSize(0), + m_dEmitterDCmosDistance(0), + m_dDCmosRCmosDistance(0), + m_nImageCmosType(0), + m_nDepthCmosType(0) +{ + m_strSensorSerial[0] = '\0'; + m_deviceInfo.strDeviceName[0] = '\0'; + m_deviceInfo.strVendorData[0] = '\0'; + m_strPlatformString[0] = '\0'; +} + +XnStatus XnSensorFixedParams::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // get fixed params + XnFixedParams FixedParams; + nRetVal = XnHostProtocolGetFixedParams(m_pDevicePrivateData, FixedParams); + if (nRetVal != XN_STATUS_OK) + { + // Ugly patch since get param is not supported in maintenance mode! + if (nRetVal != XN_STATUS_DEVICE_PROTOCOL_INVALID_COMMAND) + { + return nRetVal; + } + return nRetVal; + } + + if (m_pDevicePrivateData->FWInfo.nFWVer < XN_SENSOR_FW_VER_5_4) + { + sprintf(m_strSensorSerial, "%d", FixedParams.nSerialNumber); + } + else + { + nRetVal = XnHostProtocolGetSerialNumber(m_pDevicePrivateData, m_strSensorSerial); + + if (nRetVal != XN_STATUS_OK) + { + return nRetVal; + } + } + + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Sensor serial number: %s", m_strSensorSerial); + + // fill in properties + m_nZeroPlaneDistance = (OniDepthPixel)FixedParams.fReferenceDistance; + m_dZeroPlanePixelSize = FixedParams.fReferencePixelSize; + m_dEmitterDCmosDistance = FixedParams.fDCmosEmitterDistance; + m_dDCmosRCmosDistance = FixedParams.fDCmosRCmosDistance; + + m_nSensorDepthCMOSI2CBus = (XnUInt16)FixedParams.nDepthCmosI2CBus; + m_nSensorDepthCMOSI2CSlaveAddress = (XnUInt16)FixedParams.nDepthCmosI2CAddress; + m_nSensorImageCMOSI2CBus = (XnUInt16)FixedParams.nImageCmosI2CBus; + m_nSensorImageCMOSI2CSlaveAddress = (XnUInt16)FixedParams.nImageCmosI2CAddress; + + m_nImageCmosType = (XnUInt32)FixedParams.nImageCmosType; + m_nDepthCmosType = (XnUInt32)FixedParams.nDepthCmosType; + + nRetVal = XnHostProtocolAlgorithmParams(m_pDevicePrivateData, XN_HOST_PROTOCOL_ALGORITHM_DEVICE_INFO, + &m_deviceInfo, sizeof(m_deviceInfo), (XnResolutions)0, 0); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolGetPlatformString(m_pDevicePrivateData, m_strPlatformString); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnSensorFixedParams.h b/Source/Drivers/PS1080/Sensor/XnSensorFixedParams.h new file mode 100644 index 0000000..61f6a8e --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorFixedParams.h @@ -0,0 +1,87 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_FIXED_PARAMS_H__ +#define __XN_SENSOR_FIXED_PARAMS_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnDeviceSensor.h" + +//--------------------------------------------------------------------------- +// Forward Declarations +//--------------------------------------------------------------------------- +class XnSensorFirmware; +struct XnDevicePrivateData; +typedef struct XnDevicePrivateData XnDevicePrivateData; + +//--------------------------------------------------------------------------- +// XnSensorFixedParams class +//--------------------------------------------------------------------------- +class XnSensorFixedParams +{ +public: + XnSensorFixedParams(XnDevicePrivateData* pDevicePrivateData); + + XnStatus Init(); + + inline XnUInt16 GetDepthCmosI2CBus() const { return m_nSensorDepthCMOSI2CBus; } + inline XnUInt16 GetDepthCmosI2CSlaveAddress() const { return m_nSensorDepthCMOSI2CSlaveAddress; } + inline XnUInt16 GetImageCmosI2CBus() const { return m_nSensorImageCMOSI2CBus; } + inline XnUInt16 GetImageCmosI2CSlaveAddress() const { return m_nSensorImageCMOSI2CSlaveAddress; } + + inline OniDepthPixel GetZeroPlaneDistance() const { return m_nZeroPlaneDistance; } + inline XnDouble GetZeroPlanePixelSize() const { return m_dZeroPlanePixelSize; } + inline XnDouble GetEmitterDCmosDistance() const { return m_dEmitterDCmosDistance; } + inline XnDouble GetDCmosRCmosDistance() const { return m_dDCmosRCmosDistance; } + + inline const XnChar* GetSensorSerial() const { return m_strSensorSerial; } + + inline XnImageCMOSType GetImageCmosType() const { return (XnImageCMOSType)m_nImageCmosType; } + inline XnDepthCMOSType GetDepthCmosType() const { return (XnDepthCMOSType)m_nDepthCmosType; } + + inline const XnChar* GetDeviceName() const { return m_deviceInfo.strDeviceName; } + inline const XnChar* GetVendorData() const { return m_deviceInfo.strVendorData; } + inline const XnChar* GetPlatformString() const { return m_strPlatformString; } + +private: + XnDevicePrivateData* m_pDevicePrivateData; + + XnUInt16 m_nSensorDepthCMOSI2CBus; + XnUInt16 m_nSensorDepthCMOSI2CSlaveAddress; + XnUInt16 m_nSensorImageCMOSI2CBus; + XnUInt16 m_nSensorImageCMOSI2CSlaveAddress; + + OniDepthPixel m_nZeroPlaneDistance; + XnDouble m_dZeroPlanePixelSize; + XnDouble m_dEmitterDCmosDistance; + XnDouble m_dDCmosRCmosDistance; + + XnUInt32 m_nImageCmosType; + XnUInt32 m_nDepthCmosType; + + XnChar m_strSensorSerial[XN_DEVICE_MAX_STRING_LENGTH]; + XnDeviceInformation m_deviceInfo; + XnChar m_strPlatformString[XN_DEVICE_MAX_STRING_LENGTH]; +}; + +#endif //__XN_SENSOR_FIXED_PARAMS_H__ \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnSensorIRStream.cpp b/Source/Drivers/PS1080/Sensor/XnSensorIRStream.cpp new file mode 100644 index 0000000..31e390c --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorIRStream.cpp @@ -0,0 +1,491 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensorInit.h" +#include "XnSensorIRStream.h" +#include "XnIRProcessor.h" +#include +#include "XnCmosInfo.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_IR_MAX_BUFFER_SIZE (XN_SXGA_X_RES * XN_SXGA_Y_RES * sizeof(XnRGB24Pixel)) + +//--------------------------------------------------------------------------- +// XnSensorIRStream class +//--------------------------------------------------------------------------- +XnSensorIRStream::XnSensorIRStream(const XnChar* StreamName, XnSensorObjects* pObjects) : + XnIRStream(StreamName, FALSE), + m_InputFormat(XN_STREAM_PROPERTY_INPUT_FORMAT, "InputFormat", 0), + m_CroppingMode(XN_STREAM_PROPERTY_CROPPING_MODE, "CroppingMode", XN_CROPPING_MODE_NORMAL), + m_Helper(pObjects), + m_FirmwareCropSizeX(0, "FirmwareCropSizeX", 0, StreamName), + m_FirmwareCropSizeY(0, "FirmwareCropSizeY", 0, StreamName), + m_FirmwareCropOffsetX(0, "FirmwareCropOffsetX", 0, StreamName), + m_FirmwareCropOffsetY(0, "FirmwareCropOffsetY", 0, StreamName), + m_FirmwareCropMode(0, "FirmwareCropMode", XN_FIRMWARE_CROPPING_MODE_DISABLED, StreamName), + m_ActualRead(XN_STREAM_PROPERTY_ACTUAL_READ_DATA, "ActualReadData", FALSE) +{ + m_ActualRead.UpdateSetCallback(SetActualReadCallback, this); + m_CroppingMode.UpdateSetCallback(SetCroppingModeCallback, this); +} + +XnStatus XnSensorIRStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + +// nRetVal = SetBufferPool(&m_BufferPool); +// XN_IS_STATUS_OK(nRetVal); + + // init base + nRetVal = XnIRStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + // add properties + XN_VALIDATE_ADD_PROPERTIES(this, &m_InputFormat, &m_ActualRead, &m_CroppingMode); + + // set base properties default values + nRetVal = ResolutionProperty().UnsafeUpdateValue(XN_IR_STREAM_DEFAULT_RESOLUTION); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = FPSProperty().UnsafeUpdateValue(XN_IR_STREAM_DEFAULT_FPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = OutputFormatProperty().UnsafeUpdateValue(XN_IR_STREAM_DEFAULT_OUTPUT_FORMAT); + XN_IS_STATUS_OK(nRetVal); + + // init helper + nRetVal = m_Helper.Init(this, this); + XN_IS_STATUS_OK(nRetVal); + + // register supported modes + XnCmosPreset* pSupportedModes = m_Helper.GetPrivateData()->FWInfo.irModes.GetData(); + XnUInt32 nSupportedModes = m_Helper.GetPrivateData()->FWInfo.irModes.GetSize(); + nRetVal = AddSupportedModes(pSupportedModes, nSupportedModes); + XN_IS_STATUS_OK(nRetVal); + + // data processor + nRetVal = m_Helper.RegisterDataProcessorProperty(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal); + + // register for mirror + XnCallbackHandle hCallbackDummy; + nRetVal = IsMirroredProperty().OnChangeEvent().Register(IsMirroredChangedCallback, this, hCallbackDummy); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::Free() +{ + m_Helper.Free(); + XnIRStream::Free(); + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::MapPropertiesToFirmware() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.MapFirmwareProperty(ResolutionProperty(), GetFirmwareParams()->m_IRResolution, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(FPSProperty(), GetFirmwareParams()->m_IRFPS, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropSizeX, GetFirmwareParams()->m_IRCropSizeX, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropSizeY, GetFirmwareParams()->m_IRCropSizeY, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropOffsetX, GetFirmwareParams()->m_IRCropOffsetX, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropOffsetY, GetFirmwareParams()->m_IRCropOffsetY, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropMode, GetFirmwareParams()->m_IRCropMode, TRUE); + XN_IS_STATUS_OK(nRetVal);; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::ConfigureStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificImageUsb->pUsbConnection->UsbEp); + + nRetVal = SetActualRead(TRUE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.ConfigureFirmware(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(FPSProperty()); + XN_IS_STATUS_OK(nRetVal);; + + // IR mirror is always off in firmware + nRetVal = GetFirmwareParams()->m_IRMirror.SetValue(FALSE); + XN_IS_STATUS_OK(nRetVal); + + // CMOS + if (GetResolution() != XN_RESOLUTION_SXGA) + { + nRetVal = m_Helper.GetCmosInfo()->SetCmosConfig(XN_CMOS_TYPE_DEPTH, GetResolution(), GetFPS()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::SetActualRead(XnBool bRead) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if ((XnBool)m_ActualRead.GetValue() != bRead) + { + if (bRead) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Creating USB IR read thread..."); + XnSpecificUsbDevice* pUSB = GetHelper()->GetPrivateData()->pSpecificImageUsb; + nRetVal = xnUSBInitReadThread(pUSB->pUsbConnection->UsbEp, pUSB->nChunkReadBytes, pUSB->nNumberOfBuffers, pUSB->nTimeout, XnDeviceSensorProtocolUsbEpCb, pUSB); + XN_IS_STATUS_OK(nRetVal); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down IR image read thread..."); + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificImageUsb->pUsbConnection->UsbEp); + } + + nRetVal = m_ActualRead.UnsafeUpdateValue(bRead); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::OpenStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmwareParams()->m_Stream0Mode.SetValue(XN_VIDEO_STREAM_IR); + XN_IS_STATUS_OK(nRetVal); + + // Cropping + if (m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropSizeX); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropSizeY); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropOffsetX); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropOffsetY); + XN_IS_STATUS_OK(nRetVal);; + } + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropMode); + XN_IS_STATUS_OK(nRetVal);; + + nRetVal = FixFirmwareBug(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnIRStream::Open(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::FixFirmwareBug() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // Firmware bug ugly workaround: in v5.1, IR 1.3 would not turn off decimation, so image is + // corrupted. we need to turn it off ourselves. The problem is that the firmware does not + // even provide a way to do so, so we need to directly change the register... + // the bug only happens when cropping is off + if (m_Helper.GetFirmware()->GetInfo()->nFWVer == XN_SENSOR_FW_VER_5_1 && GetResolution() == XN_RESOLUTION_SXGA && !GetCropping()->enabled) + { + nRetVal = XnHostProtocolWriteAHB(m_Helper.GetPrivateData(), 0x2a003c00, 0x3ff0000, 0xffffffff); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::CloseStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmwareParams()->m_Stream0Mode.SetValue(XN_VIDEO_STREAM_OFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SetActualRead(FALSE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnIRStream::Close(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::SetOutputFormat(OniPixelFormat nOutputFormat) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (nOutputFormat) + { + case ONI_PIXEL_FORMAT_RGB888: + case ONI_PIXEL_FORMAT_GRAY16: + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Unsupported IR output format: %d", nOutputFormat); + } + + nRetVal = m_Helper.BeforeSettingDataProcessorProperty(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnIRStream::SetOutputFormat(nOutputFormat); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingDataProcessorProperty(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::SetFPS(XnUInt32 nFPS) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(FPSProperty(), (XnUInt16)nFPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnIRStream::SetFPS(nFPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(FPSProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::SetResolution(XnResolutions nResolution) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(ResolutionProperty(), (XnUInt16)nResolution); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnIRStream::SetResolution(nResolution); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::SetCroppingImpl(const OniCropping* pCropping, XnCroppingMode mode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFirmwareCroppingMode firmwareMode = m_Helper.GetFirmwareCroppingMode(mode, pCropping->enabled); + + nRetVal = ValidateCropping(pCropping); + XN_IS_STATUS_OK(nRetVal); + + xnOSEnterCriticalSection(GetLock()); + + if (m_Helper.GetFirmwareVersion() > XN_SENSOR_FW_VER_3_0) + { + nRetVal = m_Helper.StartFirmwareTransaction(); + if (nRetVal != XN_STATUS_OK) + { + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + // mirror is done by software (meaning AFTER cropping, which is bad). So we need to flip the cropping area + // to match requested area. + XnUInt16 nXOffset = (XnUInt16) pCropping->originX; + if (IsMirrored()) + { + nXOffset = (XnUInt16)(GetXRes() - pCropping->originX - pCropping->width); + } + + if (pCropping->enabled) + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropSizeX, (XnUInt16) pCropping->width); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropSizeY, (XnUInt16) pCropping->height); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropOffsetX, (XnUInt16) nXOffset); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropOffsetY, (XnUInt16) pCropping->originY); + } + + if (nRetVal == XN_STATUS_OK) + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropMode, (XnUInt16)firmwareMode); + } + + if (nRetVal != XN_STATUS_OK) + { + m_Helper.RollbackFirmwareTransaction(); + m_Helper.UpdateFromFirmware(m_FirmwareCropMode); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetX); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetY); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeX); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeY); + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + nRetVal = m_Helper.CommitFirmwareTransactionAsBatch(); + if (nRetVal != XN_STATUS_OK) + { + m_Helper.UpdateFromFirmware(m_FirmwareCropMode); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetX); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetY); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeX); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeY); + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + } + + nRetVal = m_CroppingMode.UnsafeUpdateValue(mode); + XN_ASSERT(nRetVal == XN_STATUS_OK); + + nRetVal = XnIRStream::SetCropping(pCropping); + if (nRetVal == XN_STATUS_OK) + { + nRetVal = FixFirmwareBug(); + } + + xnOSLeaveCriticalSection(GetLock()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::SetCropping(const OniCropping* pCropping) +{ + return SetCroppingImpl(pCropping, (XnCroppingMode)m_CroppingMode.GetValue()); +} + +XnStatus XnSensorIRStream::SetCroppingMode(XnCroppingMode mode) +{ + switch (mode) + { + case XN_CROPPING_MODE_NORMAL: + case XN_CROPPING_MODE_INCREASED_FPS: + case XN_CROPPING_MODE_SOFTWARE_ONLY: + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Bad cropping mode: %u", mode); + } + + return SetCroppingImpl(GetCropping(), mode); +} + +XnStatus XnSensorIRStream::CalcRequiredSize(XnUInt32* pnRequiredSize) const +{ + // in IR, in all resolutions except SXGA, we get additional 8 lines + XnUInt32 nYRes = GetYRes(); + if (GetResolution() != XN_RESOLUTION_SXGA) + { + nYRes += 8; + } + + *pnRequiredSize = GetXRes() * nYRes * GetBytesPerPixel(); + return XN_STATUS_OK; +} + +XnStatus XnSensorIRStream::CropImpl(OniFrame* pFrame, const OniCropping* pCropping) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // if firmware cropping is disabled, crop + if (m_FirmwareCropMode.GetValue() == XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nRetVal = XnIRStream::CropImpl(pFrame, pCropping); + XN_IS_STATUS_OK(nRetVal); + } + else if (IsMirrored()) + { + // mirror is done in software and cropping in chip, so we crop the other side (see SetCroppingImpl()). + pFrame->cropOriginX = GetXRes() - pFrame->cropOriginX - pFrame->width; + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::CreateDataProcessor(XnDataProcessor** ppProcessor) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFrameBufferManager* pBufferManager; + nRetVal = StartBufferManager(&pBufferManager); + XN_IS_STATUS_OK(nRetVal); + + XnDataProcessor* pNew; + XN_VALIDATE_NEW_AND_INIT(pNew, XnIRProcessor, this, &m_Helper, pBufferManager); + + *ppProcessor = pNew; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::OnIsMirroredChanged() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // if cropping is on, we need to flip it + OniCropping cropping = *GetCropping(); + if (cropping.enabled) + { + nRetVal = SetCropping(&cropping); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorIRStream::IsMirroredChangedCallback(const XnProperty* /*pSender*/, void* pCookie) +{ + XnSensorIRStream* pThis = (XnSensorIRStream*)pCookie; + return pThis->OnIsMirroredChanged(); +} + +XnStatus XN_CALLBACK_TYPE XnSensorIRStream::SetActualReadCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorIRStream* pThis = (XnSensorIRStream*)pCookie; + return pThis->SetActualRead(nValue == TRUE); +} + +XnStatus XN_CALLBACK_TYPE XnSensorIRStream::SetCroppingModeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorIRStream* pStream = (XnSensorIRStream*)pCookie; + return pStream->SetCroppingMode((XnCroppingMode)nValue); +} + diff --git a/Source/Drivers/PS1080/Sensor/XnSensorIRStream.h b/Source/Drivers/PS1080/Sensor/XnSensorIRStream.h new file mode 100644 index 0000000..cf217c1 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorIRStream.h @@ -0,0 +1,109 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_IR_STREAM_H__ +#define __XN_SENSOR_IR_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnSensorStreamHelper.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_IR_STREAM_DEFAULT_FPS 30 +#define XN_IR_STREAM_DEFAULT_RESOLUTION XN_RESOLUTION_QVGA +#define XN_IR_STREAM_DEFAULT_OUTPUT_FORMAT ONI_PIXEL_FORMAT_GRAY16 +#define XN_IR_STREAM_DEFAULT_MIRROR FALSE + +//--------------------------------------------------------------------------- +// XnSensorIRStream class +//--------------------------------------------------------------------------- +class XnSensorIRStream : public XnIRStream, public IXnSensorStream +{ +public: + XnSensorIRStream(const XnChar* StreamName, XnSensorObjects* pObjects); + ~XnSensorIRStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + XnStatus Free(); + XnStatus BatchConfig(const XnActualPropertiesHash& props) { return m_Helper.BatchConfig(props); } + + inline XnSensorStreamHelper* GetHelper() { return &m_Helper; } + + friend class XnIRProcessor; + +protected: + inline XnSensorFirmwareParams* GetFirmwareParams() const { return m_Helper.GetFirmware()->GetParams(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Open() { return m_Helper.Open(); } + XnStatus Close() { return m_Helper.Close(); } + XnStatus CalcRequiredSize(XnUInt32* pnRequiredSize) const; + XnStatus CropImpl(OniFrame* pFrame, const OniCropping* pCropping); + XnStatus ConfigureStreamImpl(); + XnStatus OpenStreamImpl(); + XnStatus CloseStreamImpl(); + XnStatus CreateDataProcessor(XnDataProcessor** ppProcessor); + XnStatus MapPropertiesToFirmware(); + void GetFirmwareStreamConfig(XnResolutions* pnRes, XnUInt32* pnFPS) { *pnRes = GetResolution(); *pnFPS = GetFPS(); } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + XnStatus SetOutputFormat(OniPixelFormat nOutputFormat); + XnStatus SetResolution(XnResolutions nResolution); + XnStatus SetFPS(XnUInt32 nFPS); + XnStatus SetCropping(const OniCropping* pCropping); + XnStatus SetCroppingMode(XnCroppingMode mode); + XnStatus SetActualRead(XnBool bRead); + +private: + XnStatus OnIsMirroredChanged(); + XnStatus SetCroppingImpl(const OniCropping* pCropping, XnCroppingMode mode); + + XnStatus FixFirmwareBug(); + + static XnStatus XN_CALLBACK_TYPE IsMirroredChangedCallback(const XnProperty* pSender, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetActualReadCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetCroppingModeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + XnActualIntProperty m_InputFormat; + XnActualIntProperty m_CroppingMode; + + XnSensorStreamHelper m_Helper; + XnActualIntProperty m_FirmwareCropSizeX; + XnActualIntProperty m_FirmwareCropSizeY; + XnActualIntProperty m_FirmwareCropOffsetX; + XnActualIntProperty m_FirmwareCropOffsetY; + XnActualIntProperty m_FirmwareCropMode; + + XnActualIntProperty m_ActualRead; +}; + + +#endif //__XN_SENSOR_IR_STREAM_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnSensorImageStream.cpp b/Source/Drivers/PS1080/Sensor/XnSensorImageStream.cpp new file mode 100644 index 0000000..6db1348 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorImageStream.cpp @@ -0,0 +1,943 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensorInit.h" +#include "XnSensorImageStream.h" +#include "XnSensor.h" +#include "XnBayerImageProcessor.h" +#include "XnUncompressedBayerProcessor.h" +#include "XnPSCompressedImageProcessor.h" +#include "XnJpegImageProcessor.h" +#include "XnJpegToRGBImageProcessor.h" +#include "XnPassThroughImageProcessor.h" +#include "XnUncompressedYUV422toRGBImageProcessor.h" +#include "XnUncompressedYUYVtoRGBImageProcessor.h" +#include "YUV.h" +#include "Bayer.h" +#include +#include + +//--------------------------------------------------------------------------- +// XnSensorImageStream class +//--------------------------------------------------------------------------- +XnSensorImageStream::XnSensorImageStream(const XnChar* StreamName, XnSensorObjects* pObjects) : + XnImageStream(StreamName, FALSE), + m_Helper(pObjects), + m_InputFormat(XN_STREAM_PROPERTY_INPUT_FORMAT, "InputFormat", XN_IMAGE_STREAM_DEFAULT_INPUT_FORMAT), + m_AntiFlicker(XN_STREAM_PROPERTY_FLICKER, "Flicker", XN_IMAGE_STREAM_DEFAULT_FLICKER), + m_ImageQuality(XN_STREAM_PROPERTY_QUALITY, "Quality", XN_IMAGE_STREAM_DEFAULT_QUALITY), + m_CroppingMode(XN_STREAM_PROPERTY_CROPPING_MODE, "CroppingMode", XN_CROPPING_MODE_NORMAL), + m_FirmwareMirror(0, "FirmwareMirror", FALSE, StreamName), + m_FirmwareCropSizeX(0, "FirmwareCropSizeX", 0, StreamName), + m_FirmwareCropSizeY(0, "FirmwareCropSizeY", 0, StreamName), + m_FirmwareCropOffsetX(0, "FirmwareCropOffsetX", 0, StreamName), + m_FirmwareCropOffsetY(0, "FirmwareCropOffsetY", 0, StreamName), + m_FirmwareCropMode(0, "FirmwareCropMode", XN_FIRMWARE_CROPPING_MODE_DISABLED, StreamName), + + m_AutoExposure(ONI_STREAM_PROPERTY_AUTO_EXPOSURE, "AutoExposure", XN_IMAGE_STREAM_DEFAULT_AUTO_EXPOSURE), + m_AutoWhiteBalance(ONI_STREAM_PROPERTY_AUTO_WHITE_BALANCE, "AutoWhiteBalance", XN_IMAGE_STREAM_DEFAULT_AWB), + + m_Exposure(ONI_STREAM_PROPERTY_EXPOSURE, "Exposure", XN_IMAGE_STREAM_DEFAULT_EXPOSURE_BAR), + m_Gain(ONI_STREAM_PROPERTY_GAIN, "Gain", XN_IMAGE_STREAM_DEFAULT_GAIN), + + m_ActualRead(XN_STREAM_PROPERTY_ACTUAL_READ_DATA, "ActualReadData", FALSE), + m_HorizontalFOV(ONI_STREAM_PROPERTY_HORIZONTAL_FOV, "HorizontalFov"), + m_VerticalFOV(ONI_STREAM_PROPERTY_VERTICAL_FOV, "VerticalFov") +{ +} + +XnStatus XnSensorImageStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + +// nRetVal = SetBufferPool(&m_BufferPool); +// XN_IS_STATUS_OK(nRetVal); + + // init base + nRetVal = XnImageStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + m_InputFormat.UpdateSetCallback(SetInputFormatCallback, this); + m_AntiFlicker.UpdateSetCallback(SetAntiFlickerCallback, this); + m_ImageQuality.UpdateSetCallback(SetImageQualityCallback, this); + m_CroppingMode.UpdateSetCallback(SetCroppingModeCallback, this); + m_AutoExposure.UpdateSetCallback(SetAutoExposureCallback, this); + m_Exposure.UpdateSetCallback(SetExposureCallback, this); + m_Gain.UpdateSetCallback(SetGainCallback, this); + m_AutoWhiteBalance.UpdateSetCallback(SetAutoWhiteBalanceCallback, this); + m_ActualRead.UpdateSetCallback(SetActualReadCallback, this); + + // add properties + XN_VALIDATE_ADD_PROPERTIES(this, &m_InputFormat, &m_AntiFlicker, &m_ImageQuality, + &m_CroppingMode, &m_ActualRead, &m_HorizontalFOV, &m_VerticalFOV, &m_AutoExposure, &m_AutoWhiteBalance, &m_Exposure, &m_Gain); + + // set base properties default values + nRetVal = ResolutionProperty().UnsafeUpdateValue(XN_IMAGE_STREAM_DEFAULT_RESOLUTION); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = FPSProperty().UnsafeUpdateValue(XN_IMAGE_STREAM_DEFAULT_FPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = OutputFormatProperty().UnsafeUpdateValue(XN_IMAGE_STREAM_DEFAULT_OUTPUT_FORMAT); + XN_IS_STATUS_OK(nRetVal); + + XnDouble fZPPS = m_Helper.GetFixedParams()->GetZeroPlanePixelSize(); + XnInt nZPD = m_Helper.GetFixedParams()->GetZeroPlaneDistance(); + + nRetVal = m_HorizontalFOV.UnsafeUpdateValue(2*atan(fZPPS*XN_SXGA_X_RES/2/nZPD)); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_VerticalFOV.UnsafeUpdateValue(2*atan(fZPPS*XN_VGA_Y_RES*2/2/nZPD)); + XN_IS_STATUS_OK(nRetVal); + + // init helper + nRetVal = m_Helper.Init(this, this); + XN_IS_STATUS_OK(nRetVal); + + // data processor + nRetVal = m_Helper.RegisterDataProcessorProperty(m_InputFormat); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.RegisterDataProcessorProperty(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.RegisterDataProcessorProperty(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal); + + // register supported modes + nRetVal = AddSupportedModes(m_Helper.GetPrivateData()->FWInfo.imageModes.GetData(), m_Helper.GetPrivateData()->FWInfo.imageModes.GetSize()); + XN_IS_STATUS_OK(nRetVal); + + // check if our current (default) configuration is valid + XnUInt16 nValidInputFormat = XN_IMAGE_STREAM_DEFAULT_INPUT_FORMAT; + XnBool bModeFound = FALSE; + + const xnl::Array& aSupportedModes = GetSupportedModes(); + + for (XnUInt32 i = 0; i < aSupportedModes.GetSize(); ++i) + { + if (aSupportedModes[i].nResolution == XN_IMAGE_STREAM_DEFAULT_RESOLUTION && + aSupportedModes[i].nFPS == XN_IMAGE_STREAM_DEFAULT_FPS) + { + // found + if (!bModeFound) + { + bModeFound = TRUE; + nValidInputFormat = aSupportedModes[i].nFormat; + } + + if (aSupportedModes[i].nFormat == XN_IMAGE_STREAM_DEFAULT_INPUT_FORMAT) + { + nValidInputFormat = XN_IMAGE_STREAM_DEFAULT_INPUT_FORMAT; + break; + } + } + } + + if (!bModeFound) + { + xnLogWarning(XN_MASK_DEVICE_SENSOR, "Default mode (res + FPS) is not supported by device. Changing defaults..."); + + nRetVal = ResolutionProperty().UnsafeUpdateValue(aSupportedModes[0].nResolution); + XN_IS_STATUS_OK(nRetVal); + nRetVal = FPSProperty().UnsafeUpdateValue(aSupportedModes[0].nFPS); + XN_IS_STATUS_OK(nRetVal); + nRetVal = m_InputFormat.UnsafeUpdateValue(aSupportedModes[0].nFormat); + XN_IS_STATUS_OK(nRetVal); + } + else + { + // just update input format + nRetVal = m_InputFormat.UnsafeUpdateValue(nValidInputFormat); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::Free() +{ + m_Helper.Free(); + XnImageStream::Free(); + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::MapPropertiesToFirmware() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.MapFirmwareProperty(m_InputFormat, GetFirmwareParams()->m_ImageFormat, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(ResolutionProperty(), GetFirmwareParams()->m_ImageResolution, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(FPSProperty(), GetFirmwareParams()->m_ImageFPS, FALSE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_AntiFlicker, GetFirmwareParams()->m_ImageFlickerDetection, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_ImageQuality, GetFirmwareParams()->m_ImageQuality, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareMirror, GetFirmwareParams()->m_ImageMirror, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropSizeX, GetFirmwareParams()->m_ImageCropSizeX, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropSizeY, GetFirmwareParams()->m_ImageCropSizeY, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropOffsetX, GetFirmwareParams()->m_ImageCropOffsetX, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropOffsetY, GetFirmwareParams()->m_ImageCropOffsetY, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_FirmwareCropMode, GetFirmwareParams()->m_ImageCropMode, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_AutoExposure, GetFirmwareParams()->m_ImageAutoExposure, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_AutoWhiteBalance, GetFirmwareParams()->m_ImageAutoWhiteBalance, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_Exposure, GetFirmwareParams()->m_ImageExposureBar, TRUE); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.MapFirmwareProperty(m_Gain, GetFirmwareParams()->m_ImageGain, TRUE); + XN_IS_STATUS_OK(nRetVal);; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::ValidateMode() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // validity checks + XnIOImageFormats nInputFormat = (XnIOImageFormats)m_InputFormat.GetValue(); + OniPixelFormat nOutputFormat = GetOutputFormat(); + XnResolutions nResolution = GetResolution(); + XnUInt32 nFPS = GetFPS(); + + // check that input format matches output format + switch (nOutputFormat) + { + case ONI_PIXEL_FORMAT_RGB888: + if (nInputFormat != XN_IO_IMAGE_FORMAT_YUV422 && + nInputFormat != XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422 && + nInputFormat != XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV && + nInputFormat != XN_IO_IMAGE_FORMAT_BAYER && + nInputFormat != XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Input format %d cannot be converted to RGB24!", nInputFormat); + } + break; + case ONI_PIXEL_FORMAT_YUV422: + if (nInputFormat != XN_IO_IMAGE_FORMAT_YUV422 && + nInputFormat != XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Input format %d cannot be converted to YUV422!", nInputFormat); + } + break; + case ONI_PIXEL_FORMAT_YUYV: + if (nInputFormat != XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Input format %d cannot be converted to YUYV!", nInputFormat); + } + break; + case ONI_PIXEL_FORMAT_JPEG: + if (nInputFormat != XN_IO_IMAGE_FORMAT_JPEG) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Input format %d cannot be converted to JPEG!", nInputFormat); + } + break; + case ONI_PIXEL_FORMAT_GRAY8: + if (nInputFormat != XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER && + nInputFormat != XN_IO_IMAGE_FORMAT_BAYER) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Input format %d cannot be converted to Gray8!", nInputFormat); + } + break; + default: + // we shouldn't have reached here. Theres a check at SetOutputFormat. + XN_ASSERT(FALSE); + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Unsupported image output format: %d!", nOutputFormat); + } + + // now check that mode exists + XnCmosPreset preset = { (XnUInt16)nInputFormat, (XnUInt16)nResolution, (XnUInt16)nFPS }; + nRetVal = ValidateSupportedMode(preset); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::ConfigureStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificImageUsb->pUsbConnection->UsbEp); + + nRetVal = SetActualRead(TRUE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ValidateMode(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.ConfigureFirmware(m_InputFormat); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(FPSProperty()); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_AntiFlicker); + XN_IS_STATUS_OK(nRetVal);; + + // image quality is only relevant for JPEG + if (m_InputFormat.GetValue() == XN_IO_IMAGE_FORMAT_JPEG) + { + nRetVal = m_Helper.ConfigureFirmware(m_ImageQuality); + XN_IS_STATUS_OK(nRetVal);; + } + + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareMirror); + XN_IS_STATUS_OK(nRetVal);; + + if (GetResolution() != XN_RESOLUTION_UXGA && GetResolution() != XN_RESOLUTION_SXGA) + { + nRetVal = m_Helper.GetCmosInfo()->SetCmosConfig(XN_CMOS_TYPE_IMAGE, GetResolution(), GetFPS()); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetActualRead(XnBool bRead) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if ((XnBool)m_ActualRead.GetValue() != bRead) + { + if (bRead) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Creating USB image read thread..."); + XnSpecificUsbDevice* pUSB = GetHelper()->GetPrivateData()->pSpecificImageUsb; + nRetVal = xnUSBInitReadThread(pUSB->pUsbConnection->UsbEp, pUSB->nChunkReadBytes, pUSB->nNumberOfBuffers, pUSB->nTimeout, XnDeviceSensorProtocolUsbEpCb, pUSB); + XN_IS_STATUS_OK(nRetVal); + } + else + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "Shutting down USB image read thread..."); + xnUSBShutdownReadThread(GetHelper()->GetPrivateData()->pSpecificImageUsb->pUsbConnection->UsbEp); + } + + nRetVal = m_ActualRead.UnsafeUpdateValue(bRead); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::OpenStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmwareParams()->m_Stream0Mode.SetValue(XN_VIDEO_STREAM_COLOR); + XN_IS_STATUS_OK(nRetVal); + + // Cropping + if (m_FirmwareCropMode.GetValue() != XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropSizeX); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropSizeY); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropOffsetX); + XN_IS_STATUS_OK(nRetVal);; + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropOffsetY); + XN_IS_STATUS_OK(nRetVal);; + } + nRetVal = m_Helper.ConfigureFirmware(m_FirmwareCropMode); + XN_IS_STATUS_OK(nRetVal); + + if (m_Helper.GetPrivateData()->FWInfo.bImageAdjustmentsSupported) + { + nRetVal = m_Helper.ConfigureFirmware(m_AutoExposure); + XN_IS_STATUS_OK(nRetVal); + nRetVal = m_Helper.ConfigureFirmware(m_AutoWhiteBalance); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = SetAutoExposureForOldFirmware(m_AutoExposure.GetValue() == TRUE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SetAutoWhiteBalanceForOldFirmware(m_AutoWhiteBalance.GetValue() == TRUE); + XN_IS_STATUS_OK(nRetVal); + } + + nRetVal = XnImageStream::Open(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::CloseStreamImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = GetFirmwareParams()->m_Stream0Mode.SetValue(XN_VIDEO_STREAM_OFF); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = SetActualRead(FALSE); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnImageStream::Close(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetOutputFormat(OniPixelFormat nOutputFormat) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (nOutputFormat) + { + case ONI_PIXEL_FORMAT_GRAY8: + case ONI_PIXEL_FORMAT_YUV422: + case ONI_PIXEL_FORMAT_YUYV: + case ONI_PIXEL_FORMAT_RGB888: + case ONI_PIXEL_FORMAT_JPEG: + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Unsupported image output format: %d", nOutputFormat); + } + + nRetVal = m_Helper.BeforeSettingDataProcessorProperty(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnImageStream::SetOutputFormat(nOutputFormat); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingDataProcessorProperty(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetMirror(XnBool bIsMirrored) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // set firmware mirror + XnBool bFirmwareMirror = (bIsMirrored == TRUE && m_Helper.GetFirmwareVersion() >= XN_SENSOR_FW_VER_5_0); + + xnOSEnterCriticalSection(GetLock()); + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareMirror, (XnUInt16)bFirmwareMirror); + if (nRetVal != XN_STATUS_OK) + { + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + // update prop + nRetVal = XnImageStream::SetMirror(bIsMirrored); + xnOSLeaveCriticalSection(GetLock()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetFPS(XnUInt32 nFPS) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(FPSProperty(), (XnUInt16)nFPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnImageStream::SetFPS(nFPS); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(FPSProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetResolution(XnResolutions nResolution) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.BeforeSettingFirmwareParam(ResolutionProperty(), (XnUInt16)nResolution); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnImageStream::SetResolution(nResolution); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_Helper.AfterSettingFirmwareParam(ResolutionProperty()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetInputFormat(XnIOImageFormats nInputFormat) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // validity checks + switch (nInputFormat) + { + case XN_IO_IMAGE_FORMAT_YUV422: + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422: + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV: + case XN_IO_IMAGE_FORMAT_JPEG: + case XN_IO_IMAGE_FORMAT_BAYER: + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER: + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Unknown image input format: %d", nInputFormat); + } + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_InputFormat, (XnUInt16)nInputFormat); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetAntiFlicker(XnUInt32 nFrequency) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_AntiFlicker, (XnUInt16)nFrequency); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetImageQuality(XnUInt32 /*nQuality*/) +{ + // check relevance + if (m_InputFormat.GetValue() != XN_IO_IMAGE_FORMAT_JPEG) + { + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_UNSUPPORTED_PARAMETER, XN_MASK_DEVICE_SENSOR, "Image quality is only supported when input format is JPEG"); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetCroppingImpl(const OniCropping* pCropping, XnCroppingMode mode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFirmwareCroppingMode firmwareMode = m_Helper.GetFirmwareCroppingMode(mode, pCropping->enabled); + + nRetVal = ValidateCropping(pCropping); + XN_IS_STATUS_OK(nRetVal); + + xnOSEnterCriticalSection(GetLock()); + + if (m_Helper.GetFirmwareVersion() > XN_SENSOR_FW_VER_3_0) + { + nRetVal = m_Helper.StartFirmwareTransaction(); + if (nRetVal != XN_STATUS_OK) + { + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + if (pCropping->enabled) + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropSizeX, (XnUInt16) pCropping->width); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropSizeY, (XnUInt16) pCropping->height); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropOffsetX, (XnUInt16) pCropping->originX); + + if (nRetVal == XN_STATUS_OK) + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropOffsetY, (XnUInt16) pCropping->originY); + } + + if (nRetVal == XN_STATUS_OK) + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_FirmwareCropMode, (XnUInt16)firmwareMode); + } + + if (nRetVal != XN_STATUS_OK) + { + m_Helper.RollbackFirmwareTransaction(); + m_Helper.UpdateFromFirmware(m_FirmwareCropMode); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetX); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetY); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeX); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeY); + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + + nRetVal = m_Helper.CommitFirmwareTransactionAsBatch(); + if (nRetVal != XN_STATUS_OK) + { + m_Helper.UpdateFromFirmware(m_FirmwareCropMode); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetX); + m_Helper.UpdateFromFirmware(m_FirmwareCropOffsetY); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeX); + m_Helper.UpdateFromFirmware(m_FirmwareCropSizeY); + xnOSLeaveCriticalSection(GetLock()); + return (nRetVal); + } + } + + nRetVal = m_CroppingMode.UnsafeUpdateValue(mode); + XN_ASSERT(nRetVal == XN_STATUS_OK); + + nRetVal = XnImageStream::SetCropping(pCropping); + xnOSLeaveCriticalSection(GetLock()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetCropping(const OniCropping* pCropping) +{ + return SetCroppingImpl(pCropping, (XnCroppingMode)m_CroppingMode.GetValue()); +} + +XnStatus XnSensorImageStream::SetCroppingMode(XnCroppingMode mode) +{ + switch (mode) + { + case XN_CROPPING_MODE_NORMAL: + case XN_CROPPING_MODE_INCREASED_FPS: + case XN_CROPPING_MODE_SOFTWARE_ONLY: + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_DEVICE_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "Bad cropping mode: %u", mode); + } + + return SetCroppingImpl(GetCropping(), mode); +} + +XnStatus XnSensorImageStream::SetAutoExposureForOldFirmware(XnBool bAutoExposure) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt16 nCmosRegValue; + + nRetVal = XnHostProtocolSetCMOSRegisterI2C(m_Helper.GetPrivateData(), (XnCMOSType)0, 0xf0, 1); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolGetCMOSRegisterI2C(m_Helper.GetPrivateData(), (XnCMOSType)0, 0x6, nCmosRegValue); + XN_IS_STATUS_OK(nRetVal); + + if (bAutoExposure) + { + nCmosRegValue |= 0x4000; + } + else + { + nCmosRegValue &= ~0x4000; + } + + nRetVal = XnHostProtocolSetCMOSRegisterI2C(m_Helper.GetPrivateData(), (XnCMOSType)0, 0x6, nCmosRegValue); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnSensorImageStream::SetAutoExposure(XnBool bAutoExposure) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_Helper.GetPrivateData()->FWInfo.bImageAdjustmentsSupported) + { + nRetVal = SetAutoExposureForOldFirmware(bAutoExposure); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_AutoExposure.UnsafeUpdateValue(bAutoExposure); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_AutoExposure, (XnUInt16)bAutoExposure); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetAutoWhiteBalanceForOldFirmware(XnBool bAutoWhiteBalance) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt16 nCmosRegValue; + + nRetVal = XnHostProtocolSetCMOSRegisterI2C(m_Helper.GetPrivateData(), (XnCMOSType)0, 0xf0, 1); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = XnHostProtocolGetCMOSRegisterI2C(m_Helper.GetPrivateData(), (XnCMOSType)0, 0x6, nCmosRegValue); + XN_IS_STATUS_OK(nRetVal); + + if (bAutoWhiteBalance) + { + nCmosRegValue |= 0x2; + } + else + { + nCmosRegValue &= ~0x2; + } + + nRetVal = XnHostProtocolSetCMOSRegisterI2C(m_Helper.GetPrivateData(), (XnCMOSType)0, 0x6, nCmosRegValue); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus XnSensorImageStream::SetAutoWhiteBalance(XnBool bAutoWhiteBalance) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_Helper.GetPrivateData()->FWInfo.bImageAdjustmentsSupported) + { + nRetVal = SetAutoWhiteBalanceForOldFirmware(bAutoWhiteBalance); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_AutoWhiteBalance.UnsafeUpdateValue(bAutoWhiteBalance); + XN_IS_STATUS_OK(nRetVal); + } + else + { + nRetVal = m_Helper.SimpleSetFirmwareParam(m_AutoWhiteBalance, (XnUInt16)bAutoWhiteBalance); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::SetExposure(XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_Helper.GetPrivateData()->FWInfo.bImageAdjustmentsSupported) + { + return (XN_STATUS_UNSUPPORTED_VERSION); + } + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_Exposure, (XnUInt16)nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} +XnStatus XnSensorImageStream::SetGain(XnUInt64 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_Helper.GetPrivateData()->FWInfo.bImageAdjustmentsSupported) + { + return (XN_STATUS_UNSUPPORTED_VERSION); + } + + nRetVal = m_Helper.SimpleSetFirmwareParam(m_Gain, (XnUInt16)nValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::CropImpl(OniFrame* pFrame, const OniCropping* pCropping) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // if firmware cropping is disabled, crop + if (m_FirmwareCropMode.GetValue() == XN_FIRMWARE_CROPPING_MODE_DISABLED) + { + nRetVal = XnImageStream::CropImpl(pFrame, pCropping); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorImageStream::Mirror(OniFrame* pFrame) const +{ + XnStatus nRetVal = XN_STATUS_OK; + + // only perform mirror if it's our job. if mirror is performed by FW, we don't need to do anything. + if (m_FirmwareMirror.GetValue() == FALSE) + { + nRetVal = XnImageStream::Mirror(pFrame); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnUInt32 XnSensorImageStream::CalculateExpectedSize() +{ + XnUInt32 nExpectedImageBufferSize = GetXRes() * GetYRes(); + + // when cropping is turned on, actual IR size is smaller + const OniCropping* pCropping = GetCropping(); + if (pCropping->enabled) + { + nExpectedImageBufferSize = pCropping->width * pCropping->height; + } + + switch (m_InputFormat.GetValue()) + { + case XN_IO_IMAGE_FORMAT_YUV422: + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422: + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV: + // in YUV each pixel is represented in 2 bytes (actually 2 pixels are represented by 4 bytes) + nExpectedImageBufferSize *= 2; + break; + case XN_IO_IMAGE_FORMAT_BAYER: + // each pixel is one byte. + break; + case XN_IO_IMAGE_FORMAT_JPEG: + // image should be in RGB now - 3 bytes a pixel + nExpectedImageBufferSize *= 3; + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_DEVICE_SENSOR, "Does not know to calculate expected size for input format %d", m_InputFormat.GetValue()); + } + + return nExpectedImageBufferSize; +} + +XnStatus XnSensorImageStream::CreateDataProcessor(XnDataProcessor** ppProcessor) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnFrameBufferManager* pBufferManager; + nRetVal = StartBufferManager(&pBufferManager); + XN_IS_STATUS_OK(nRetVal); + + XnStreamProcessor* pNew; + + switch (m_InputFormat.GetValue()) + { + case XN_IO_IMAGE_FORMAT_BAYER: + XN_VALIDATE_NEW_AND_INIT(pNew, XnBayerImageProcessor, this, &m_Helper, pBufferManager); + break; + case XN_IO_IMAGE_FORMAT_YUV422: + XN_VALIDATE_NEW_AND_INIT(pNew, XnPSCompressedImageProcessor, this, &m_Helper, pBufferManager); + break; + case XN_IO_IMAGE_FORMAT_JPEG: + if (GetOutputFormat() == ONI_PIXEL_FORMAT_JPEG) + { + XN_VALIDATE_NEW_AND_INIT(pNew, XnJpegImageProcessor, this, &m_Helper, pBufferManager); + } + else if (GetOutputFormat() == ONI_PIXEL_FORMAT_RGB888) + { + XN_VALIDATE_NEW_AND_INIT(pNew, XnJpegToRGBImageProcessor, this, &m_Helper, pBufferManager); + } + else + { + XN_LOG_WARNING_RETURN(XN_STATUS_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "invalid output format %d!", GetOutputFormat()); + } + break; + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422: + if (GetOutputFormat() == ONI_PIXEL_FORMAT_YUV422) + { + XN_VALIDATE_NEW_AND_INIT(pNew, XnPassThroughImageProcessor, this, &m_Helper, pBufferManager); + } + else if (GetOutputFormat() == ONI_PIXEL_FORMAT_RGB888) + { + XN_VALIDATE_NEW_AND_INIT(pNew, XnUncompressedYUV422toRGBImageProcessor, this, &m_Helper, pBufferManager); + } + else + { + XN_LOG_WARNING_RETURN(XN_STATUS_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "invalid output format %d!", pBufferManager); + } + break; + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUYV: + if (GetOutputFormat() == ONI_PIXEL_FORMAT_YUYV) + { + XN_VALIDATE_NEW_AND_INIT(pNew, XnPassThroughImageProcessor, this, &m_Helper, pBufferManager); + } + else if (GetOutputFormat() == ONI_PIXEL_FORMAT_RGB888) + { + XN_VALIDATE_NEW_AND_INIT(pNew, XnUncompressedYUYVtoRGBImageProcessor, this, &m_Helper, pBufferManager); + } + else + { + XN_LOG_WARNING_RETURN(XN_STATUS_BAD_PARAM, XN_MASK_DEVICE_SENSOR, "invalid output format %d!", pBufferManager); + } + break; + case XN_IO_IMAGE_FORMAT_UNCOMPRESSED_BAYER: + XN_VALIDATE_NEW_AND_INIT(pNew, XnUncompressedBayerProcessor, this, &m_Helper, pBufferManager); + break; + default: + return XN_STATUS_IO_INVALID_STREAM_IMAGE_FORMAT; + } + + *ppProcessor = pNew; + + return XN_STATUS_OK; +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetInputFormatCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pThis = (XnSensorImageStream*)pCookie; + return pThis->SetInputFormat((XnIOImageFormats)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetAntiFlickerCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pThis = (XnSensorImageStream*)pCookie; + return pThis->SetAntiFlicker((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetImageQualityCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pThis = (XnSensorImageStream*)pCookie; + return pThis->SetImageQuality((XnUInt32)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetActualReadCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pThis = (XnSensorImageStream*)pCookie; + return pThis->SetActualRead(nValue == TRUE); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetCroppingModeCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pStream = (XnSensorImageStream*)pCookie; + return pStream->SetCroppingMode((XnCroppingMode)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetAutoExposureCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pStream = (XnSensorImageStream*)pCookie; + return pStream->SetAutoExposure((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetAutoWhiteBalanceCallback(XnActualIntProperty* /*pSender*/, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pStream = (XnSensorImageStream*)pCookie; + return pStream->SetAutoWhiteBalance((XnBool)nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetExposureCallback(XnActualIntProperty*, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pStream = (XnSensorImageStream*)pCookie; + return pStream->SetExposure(nValue); +} + +XnStatus XN_CALLBACK_TYPE XnSensorImageStream::SetGainCallback(XnActualIntProperty*, XnUInt64 nValue, void* pCookie) +{ + XnSensorImageStream* pStream = (XnSensorImageStream*)pCookie; + return pStream->SetGain(nValue); +} \ No newline at end of file diff --git a/Source/Drivers/PS1080/Sensor/XnSensorImageStream.h b/Source/Drivers/PS1080/Sensor/XnSensorImageStream.h new file mode 100644 index 0000000..5d30c7c --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorImageStream.h @@ -0,0 +1,159 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_IMAGE_STREAM_H__ +#define __XN_SENSOR_IMAGE_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnSensorStreamHelper.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_IMAGE_STREAM_DEFAULT_FPS 30 +#define XN_IMAGE_STREAM_DEFAULT_RESOLUTION XN_RESOLUTION_QVGA +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + #define XN_IMAGE_STREAM_DEFAULT_INPUT_FORMAT XN_IO_IMAGE_FORMAT_UNCOMPRESSED_YUV422 +#else + //non win32 works with BULK, so it's better to compress the image + #define XN_IMAGE_STREAM_DEFAULT_INPUT_FORMAT XN_IO_IMAGE_FORMAT_YUV422 +#endif +#define XN_IMAGE_STREAM_DEFAULT_OUTPUT_FORMAT ONI_PIXEL_FORMAT_RGB888 +#define XN_IMAGE_STREAM_DEFAULT_FLICKER 0 +#define XN_IMAGE_STREAM_DEFAULT_QUALITY 3 +#define XN_IMAGE_STREAM_DEFAULT_BRIGHTNESS 128 +#define XN_IMAGE_STREAM_DEFAULT_CONTRAST 32 +#define XN_IMAGE_STREAM_DEFAULT_SATURATION 128 +#define XN_IMAGE_STREAM_DEFAULT_SHARPNESS 32 +#define XN_IMAGE_STREAM_DEFAULT_AWB TRUE +#define XN_IMAGE_STREAM_DEFAULT_COLOR_TEMP 5000 +#define XN_IMAGE_STREAM_DEFAULT_BACKLIGHT_COMP 1 +#define XN_IMAGE_STREAM_DEFAULT_GAIN 100 +#define XN_IMAGE_STREAM_DEFAULT_ZOOM 100 +#define XN_IMAGE_STREAM_DEFAULT_AUTO_EXPOSURE TRUE +#define XN_IMAGE_STREAM_DEFAULT_EXPOSURE_BAR 0 +#define XN_IMAGE_STREAM_DEFAULT_PAN 0 +#define XN_IMAGE_STREAM_DEFAULT_TILT 0 +#define XN_IMAGE_STREAM_DEFAULT_LOW_LIGHT_COMP TRUE + +//--------------------------------------------------------------------------- +// XnSensorImageStream class +//--------------------------------------------------------------------------- +class XnSensorImageStream : public XnImageStream, public IXnSensorStream +{ +public: + XnSensorImageStream(const XnChar* StreamName, XnSensorObjects* pObjects); + ~XnSensorImageStream() { Free(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Init(); + XnStatus Free(); + XnStatus BatchConfig(const XnActualPropertiesHash& props) { return m_Helper.BatchConfig(props); } + + XnUInt32 CalculateExpectedSize(); + + inline XnSensorStreamHelper* GetHelper() { return &m_Helper; } + + friend class XnImageProcessor; + +protected: + inline XnSensorFirmwareParams* GetFirmwareParams() const { return m_Helper.GetFirmware()->GetParams(); } + + //--------------------------------------------------------------------------- + // Overridden Methods + //--------------------------------------------------------------------------- + XnStatus Open() { return m_Helper.Open(); } + XnStatus Close() { return m_Helper.Close(); } + XnStatus CropImpl(OniFrame* pFrame, const OniCropping* pCropping); + XnStatus Mirror(OniFrame* pFrame) const; + XnStatus ConfigureStreamImpl(); + XnStatus OpenStreamImpl(); + XnStatus CloseStreamImpl(); + XnStatus CreateDataProcessor(XnDataProcessor** ppProcessor); + XnStatus MapPropertiesToFirmware(); + void GetFirmwareStreamConfig(XnResolutions* pnRes, XnUInt32* pnFPS) { *pnRes = GetResolution(); *pnFPS = GetFPS(); } + + //--------------------------------------------------------------------------- + // Setters + //--------------------------------------------------------------------------- + XnStatus SetOutputFormat(OniPixelFormat nOutputFormat); + XnStatus SetMirror(XnBool bIsMirrored); + XnStatus SetResolution(XnResolutions nResolution); + XnStatus SetFPS(XnUInt32 nFPS); + virtual XnStatus SetInputFormat(XnIOImageFormats nInputFormat); + virtual XnStatus SetAntiFlicker(XnUInt32 nFrequency); + virtual XnStatus SetImageQuality(XnUInt32 nQuality); + virtual XnStatus SetCropping(const OniCropping* pCropping); + virtual XnStatus SetCroppingMode(XnCroppingMode mode); + XnStatus SetActualRead(XnBool bRead); + virtual XnStatus SetAutoExposure(XnBool bAutoExposure); + virtual XnStatus SetAutoWhiteBalance(XnBool bAutoWhiteBalance); + virtual XnStatus SetExposure(XnUInt64 nValue); + virtual XnStatus SetGain(XnUInt64 nValue); +private: + XnStatus ValidateMode(); + XnStatus SetCroppingImpl(const OniCropping* pCropping, XnCroppingMode mode); + XnStatus SetAutoExposureForOldFirmware(XnBool bAutoExposure); + XnStatus SetAutoWhiteBalanceForOldFirmware(XnBool bAutoWhiteBalance); + + static XnStatus XN_CALLBACK_TYPE SetInputFormatCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetAntiFlickerCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetImageQualityCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetActualReadCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetCroppingModeCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetAutoExposureCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetAutoWhiteBalanceCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetExposureCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + static XnStatus XN_CALLBACK_TYPE SetGainCallback(XnActualIntProperty* pSender, XnUInt64 nValue, void* pCookie); + + //--------------------------------------------------------------------------- + // Members + //--------------------------------------------------------------------------- + XnSensorStreamHelper m_Helper; + XnActualIntProperty m_InputFormat; + XnActualIntProperty m_AntiFlicker; + XnActualIntProperty m_ImageQuality; + XnActualIntProperty m_CroppingMode; + + XnActualIntProperty m_FirmwareMirror; + + XnActualIntProperty m_FirmwareCropSizeX; + XnActualIntProperty m_FirmwareCropSizeY; + XnActualIntProperty m_FirmwareCropOffsetX; + XnActualIntProperty m_FirmwareCropOffsetY; + XnActualIntProperty m_FirmwareCropMode; + + XnActualIntProperty m_AutoExposure; + XnActualIntProperty m_AutoWhiteBalance; + XnActualIntProperty m_Exposure; + XnActualIntProperty m_Gain; + + XnActualIntProperty m_ActualRead; + + XnActualRealProperty m_HorizontalFOV; + XnActualRealProperty m_VerticalFOV; +}; + +#endif //__XN_SENSOR_IMAGE_STREAM_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.cpp b/Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.cpp new file mode 100644 index 0000000..27d1816 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.cpp @@ -0,0 +1,419 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnSensorStreamHelper.h" +#include "XnStreamProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnSensorStreamHelper::XnSensorStreamHelper(XnSensorObjects* pObjects) : + m_pSensorStream(NULL), + m_pStream(NULL), + m_pObjects(pObjects) +{ +} + +XnSensorStreamHelper::~XnSensorStreamHelper() +{ + Free(); +} + +XnStatus XnSensorStreamHelper::Init(IXnSensorStream* pSensorStream, XnDeviceStream* pStream) +{ + XnStatus nRetVal = XN_STATUS_OK; + + m_pSensorStream = pSensorStream; + m_pStream = pStream; + + nRetVal = m_pSensorStream->MapPropertiesToFirmware(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::Free() +{ + if (m_pStream != NULL) + { + GetFirmware()->GetStreams()->ReleaseStream(m_pStream->GetType(), m_pStream); + } + + m_FirmwareProperties.Clear(); + + return XN_STATUS_OK; +} + +XnStatus XnSensorStreamHelper::Configure() +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnResolutions nRes; + XnUInt32 nFPS; + m_pSensorStream->GetFirmwareStreamConfig(&nRes, &nFPS); + + // claim the stream + nRetVal = GetFirmware()->GetStreams()->ClaimStream(m_pStream->GetType(), nRes, nFPS, m_pStream); + XN_IS_STATUS_OK(nRetVal); + + // configure the stream + nRetVal = m_pSensorStream->ConfigureStreamImpl(); + if (nRetVal != XN_STATUS_OK) + { + GetFirmware()->GetStreams()->ReleaseStream(m_pStream->GetType(), m_pStream); + return (nRetVal); + } + + // create data processor + XnDataProcessor* pProcessor; + nRetVal = m_pSensorStream->CreateDataProcessor(&pProcessor); + if (nRetVal != XN_STATUS_OK) + { + GetFirmware()->GetStreams()->ReleaseStream(m_pStream->GetType(), m_pStream); + return (nRetVal); + } + + // and register it + nRetVal = GetFirmware()->GetStreams()->ReplaceStreamProcessor(m_pStream->GetType(), m_pStream, pProcessor); + if (nRetVal != XN_STATUS_OK) + { + GetFirmware()->GetStreams()->ReleaseStream(m_pStream->GetType(), m_pStream); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::FinalOpen() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_pSensorStream->OpenStreamImpl(); + if (nRetVal != XN_STATUS_OK) + { + GetFirmware()->GetStreams()->ReleaseStream(m_pStream->GetType(), m_pStream); + return (nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::Open() +{ + XnStatus nRetVal = XN_STATUS_OK; + + // configure the stream + nRetVal = Configure(); + XN_IS_STATUS_OK(nRetVal); + + // Update frequency (it might change on specific stream configuration) + XnFrequencyInformation FrequencyInformation; + nRetVal = XnHostProtocolAlgorithmParams(m_pObjects->pDevicePrivateData, XN_HOST_PROTOCOL_ALGORITHM_FREQUENCY, &FrequencyInformation, sizeof(XnFrequencyInformation), (XnResolutions)0, 0); + XN_IS_STATUS_OK(nRetVal); + + m_pObjects->pDevicePrivateData->fDeviceFrequency = XN_PREPARE_VAR_FLOAT_IN_BUFFER(FrequencyInformation.fDeviceFrequency); + + // and now turn it on + nRetVal = FinalOpen(); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::Close() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (GetFirmware()->GetStreams()->IsClaimed(m_pStream->GetType(), m_pStream)) + { + nRetVal = m_pSensorStream->CloseStreamImpl(); + XN_IS_STATUS_OK(nRetVal); + + GetFirmware()->GetStreams()->ReleaseStream(m_pStream->GetType(), m_pStream); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::RegisterDataProcessorProperty(XnActualIntProperty& Property) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // mark it so + XnSensorStreamHelperCookie* pCookie; + nRetVal = m_FirmwareProperties.Get(&Property, pCookie); + XN_IS_STATUS_OK(nRetVal); + + pCookie->bProcessorProp = TRUE; + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::MapFirmwareProperty(XnActualIntProperty& Property, XnActualIntProperty& FirmwareProperty, XnBool bAllowChangeWhileOpen, XnSensorStreamHelper::ConvertCallback pStreamToFirmwareFunc /* = 0 */) +{ + XnStatus nRetVal = XN_STATUS_OK; + + // init data + XnSensorStreamHelperCookie cookie(&Property, &FirmwareProperty, bAllowChangeWhileOpen, pStreamToFirmwareFunc); + + // add it to the list + nRetVal = m_FirmwareProperties.Set(&Property, cookie); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::ConfigureFirmware(XnActualIntProperty& Property) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnSensorStreamHelperCookie* pPropData = NULL; + nRetVal = m_FirmwareProperties.Get(&Property, pPropData); + XN_IS_STATUS_OK(nRetVal); + + XnUInt64 nFirmwareValue = Property.GetValue(); + + if (pPropData->pStreamToFirmwareFunc != NULL) + { + nRetVal = pPropData->pStreamToFirmwareFunc(Property.GetValue(), &nFirmwareValue); + XN_IS_STATUS_OK(nRetVal); + } + + nRetVal = pPropData->pFirmwareProp->SetValue(nFirmwareValue); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::BeforeSettingFirmwareParam(XnActualIntProperty& Property, XnUInt16 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnSensorStreamHelperCookie* pPropData = NULL; + nRetVal = m_FirmwareProperties.Get(&Property, pPropData); + XN_IS_STATUS_OK(nRetVal); + + pPropData->CurrentTransaction.bShouldOpen = FALSE; + pPropData->CurrentTransaction.bChooseProcessor = FALSE; + + // if stream is closed, we can just update the prop. + if (m_pStream->IsOpen()) + { + // check if we need to close the stream first + if (pPropData->bAllowWhileOpen) + { + // before actual changing it, check if this is a processor property + if (pPropData->bProcessorProp) + { + // lock processor + nRetVal = GetFirmware()->GetStreams()->LockStreamProcessor(m_pStream->GetType(), m_pStream); + XN_IS_STATUS_OK(nRetVal); + pPropData->CurrentTransaction.bChooseProcessor = TRUE; + } + + // OK. change the value + XnUInt64 nFirmwareValue = nValue; + + if (pPropData->pStreamToFirmwareFunc != NULL) + { + nRetVal = pPropData->pStreamToFirmwareFunc(nValue, &nFirmwareValue); + XN_IS_STATUS_OK(nRetVal); + } + + // set the param in firmware + nRetVal = pPropData->pFirmwareProp->SetValue(nFirmwareValue); + XN_IS_STATUS_OK(nRetVal); + + // no need to do anything after property will be set + pPropData->CurrentTransaction.bShouldOpen = FALSE; + } + else + { + // we can't change the firmware param. We should first close the stream + nRetVal = m_pStream->Close(); + XN_IS_STATUS_OK(nRetVal); + + // after property will be set, we need to reopen the stream + pPropData->CurrentTransaction.bShouldOpen = TRUE; + } + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::AfterSettingFirmwareParam(XnActualIntProperty& Property) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnSensorStreamHelperCookie* pPropData = NULL; + nRetVal = m_FirmwareProperties.Get(&Property, pPropData); + XN_IS_STATUS_OK(nRetVal); + + if (pPropData->CurrentTransaction.bShouldOpen) + { + nRetVal = m_pStream->Open(); + XN_IS_STATUS_OK(nRetVal); + } + else if (pPropData->CurrentTransaction.bChooseProcessor) + { + XnDataProcessor* pProcessor = NULL; + nRetVal = m_pSensorStream->CreateDataProcessor(&pProcessor); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetFirmware()->GetStreams()->ReplaceStreamProcessor(m_pStream->GetType(), m_pStream, pProcessor); + XN_IS_STATUS_OK(nRetVal); + + // and unlock + nRetVal = GetFirmware()->GetStreams()->UnlockStreamProcessor(m_pStream->GetType(), m_pStream); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::SimpleSetFirmwareParam(XnActualIntProperty& Property, XnUInt16 nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = BeforeSettingFirmwareParam(Property, nValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = Property.UnsafeUpdateValue(nValue); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = AfterSettingFirmwareParam(Property); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::BeforeSettingDataProcessorProperty() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_pStream->IsOpen()) + { + nRetVal = GetFirmware()->GetStreams()->LockStreamProcessor(m_pStream->GetType(), m_pStream); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::AfterSettingDataProcessorProperty() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_pStream->IsOpen()) + { + XnDataProcessor* pProcessor = NULL; + nRetVal = m_pSensorStream->CreateDataProcessor(&pProcessor); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = GetFirmware()->GetStreams()->ReplaceStreamProcessor(m_pStream->GetType(), m_pStream, pProcessor); + XN_IS_STATUS_OK(nRetVal); + + // and unlock + nRetVal = GetFirmware()->GetStreams()->UnlockStreamProcessor(m_pStream->GetType(), m_pStream); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::UpdateFromFirmware(XnActualIntProperty& Property) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnSensorStreamHelperCookie* pPropData = NULL; + nRetVal = m_FirmwareProperties.Get(&Property, pPropData); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = pPropData->pStreamProp->UnsafeUpdateValue(pPropData->pFirmwareProp->GetValue()); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus XnSensorStreamHelper::BatchConfig(const XnActualPropertiesHash& props) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnBool bShouldClose = FALSE; + + if (m_pStream->IsOpen()) + { + // check if one of the properties requires to close the stream + for (FirmareProperties::Iterator it = m_FirmwareProperties.Begin(); it != m_FirmwareProperties.End(); ++it) + { + if (!it->Value().bAllowWhileOpen) + { + XnProperty* pProp; + if (XN_STATUS_OK == props.Get(it->Value().pStreamProp->GetId(), pProp)) + { + bShouldClose = TRUE; + break; + } + } + } + } + + if (bShouldClose) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "closing stream before batch config..."); + nRetVal = m_pStream->Close(); + XN_IS_STATUS_OK(nRetVal); + } + + nRetVal = m_pStream->XnDeviceStream::BatchConfig(props); + XN_IS_STATUS_OK(nRetVal); + + if (bShouldClose) + { + xnLogVerbose(XN_MASK_DEVICE_SENSOR, "re-opening stream after batch config..."); + nRetVal = m_pStream->Open(); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnFirmwareCroppingMode XnSensorStreamHelper::GetFirmwareCroppingMode(XnCroppingMode nValue, XnBool bEnabled) +{ + if (!bEnabled) + return XN_FIRMWARE_CROPPING_MODE_DISABLED; + + switch (nValue) + { + case XN_CROPPING_MODE_NORMAL: + return XN_FIRMWARE_CROPPING_MODE_NORMAL; + case XN_CROPPING_MODE_INCREASED_FPS: + return GetFirmware()->GetInfo()->bIncreasedFpsCropSupported ? XN_FIRMWARE_CROPPING_MODE_INCREASED_FPS : XN_FIRMWARE_CROPPING_MODE_NORMAL; + case XN_CROPPING_MODE_SOFTWARE_ONLY: + return XN_FIRMWARE_CROPPING_MODE_DISABLED; + default: + return XN_FIRMWARE_CROPPING_MODE_NORMAL; + } +} diff --git a/Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.h b/Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.h new file mode 100644 index 0000000..947ec21 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnSensorStreamHelper.h @@ -0,0 +1,143 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_SENSOR_STREAM_HELPER_H__ +#define __XN_SENSOR_STREAM_HELPER_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "IXnSensorStream.h" +#include "XnSensorFirmware.h" +#include "XnSensorFixedParams.h" +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnSensorStreamHelper +{ +public: + XnSensorStreamHelper(XnSensorObjects* pObjects); + ~XnSensorStreamHelper(); + + XnStatus Init(IXnSensorStream* pSensorStream, XnDeviceStream* pStream); + XnStatus Free(); + + XnStatus Configure(); + XnStatus FinalOpen(); + XnStatus Open(); + XnStatus Close(); + + /** + * Registers a property which affects the data processor. When any of these properties + * changes, the data processor will be recreated. + */ + XnStatus RegisterDataProcessorProperty(XnActualIntProperty& Property); + + typedef XnStatus (*ConvertCallback)(XnUInt64 nSource, XnUInt64* pnDest); + + /** + * Maps a stream property to a firmware property. Later on, such a property can be used + * in calls to ConfigureFirmware or SetStreamFirmwareParam. + */ + XnStatus MapFirmwareProperty(XnActualIntProperty& Property, XnActualIntProperty& FirmwareProperty, XnBool bAllowChangeWhileOpen, ConvertCallback pStreamToFirmwareFunc = 0); + + /** + * Configures the firmware according to the property. This can only be done for properties + * which were previously attached via the MapFirmwareProperty function. + */ + XnStatus ConfigureFirmware(XnActualIntProperty& Property); + + XnStatus BeforeSettingFirmwareParam(XnActualIntProperty& Property, XnUInt16 nValue); + XnStatus AfterSettingFirmwareParam(XnActualIntProperty& Property); + + XnStatus BeforeSettingDataProcessorProperty(); + XnStatus AfterSettingDataProcessorProperty(); + + XnStatus SimpleSetFirmwareParam(XnActualIntProperty& Property, XnUInt16 nValue); + + XnStatus UpdateFromFirmware(XnActualIntProperty& Property); + + inline XnSensorFirmware* GetFirmware() const { return m_pObjects->pFirmware; } + inline XnFWVer GetFirmwareVersion() const { return GetFirmware()->GetInfo()->nFWVer; } + inline XnSensorFixedParams* GetFixedParams() const { return m_pObjects->pFirmware->GetFixedParams(); } + inline XnDevicePrivateData* GetPrivateData() const { return m_pObjects->pDevicePrivateData; } + inline XnSensorFPS* GetFPS() const { return m_pObjects->pFPS; } + inline XnCmosInfo* GetCmosInfo() const { return m_pObjects->pCmosInfo; } + inline IXnSensorStream* GetSensorStream() { return m_pSensorStream; } + + inline XnStatus StartFirmwareTransaction() { return GetFirmware()->GetParams()->StartTransaction(); } + inline XnStatus CommitFirmwareTransaction() { return GetFirmware()->GetParams()->CommitTransaction(); } + inline XnStatus CommitFirmwareTransactionAsBatch() { return GetFirmware()->GetParams()->CommitTransactionAsBatch(); } + inline XnStatus RollbackFirmwareTransaction() { return GetFirmware()->GetParams()->RollbackTransaction(); } + + XnStatus BatchConfig(const XnActualPropertiesHash& props); + + XnFirmwareCroppingMode GetFirmwareCroppingMode(XnCroppingMode nValue, XnBool bEnabled); + +private: + IXnSensorStream* m_pSensorStream; + XnDeviceStream* m_pStream; + XnSensorObjects* m_pObjects; + + class XnSensorStreamHelperCookie + { + public: + XnSensorStreamHelperCookie() {} + XnSensorStreamHelperCookie(XnActualIntProperty* pStreamProp, XnActualIntProperty* pFirmwareProp, XnBool bAllowWhileOpen, XnSensorStreamHelper::ConvertCallback pStreamToFirmwareFunc) : + pStreamProp(pStreamProp), pFirmwareProp(pFirmwareProp), bAllowWhileOpen(bAllowWhileOpen), pStreamToFirmwareFunc(pStreamToFirmwareFunc), bProcessorProp(FALSE) + {} + + XnActualIntProperty* pStreamProp; + XnActualIntProperty* pFirmwareProp; + XnBool bAllowWhileOpen; + XnSensorStreamHelper::ConvertCallback pStreamToFirmwareFunc; + XnBool bProcessorProp; + + struct + { + XnBool bShouldOpen; + XnBool bChooseProcessor; + } CurrentTransaction; + }; + + typedef xnl::Hash FirmareProperties; + FirmareProperties m_FirmwareProperties; +}; + +class XnSensorStreamHolder : public XnDeviceModuleHolder +{ +public: + XnSensorStreamHolder(XnDeviceStream* pStream, XnSensorStreamHelper* pHelper) : + XnDeviceModuleHolder(pStream), m_pHelper(pHelper) + {} + + inline XnDeviceStream* GetStream() { return (XnDeviceStream*)GetModule(); } + + XnStatus Configure() { return m_pHelper->Configure(); } + XnStatus FinalOpen() { return m_pHelper->FinalOpen(); } + +private: + XnSensorStreamHelper* m_pHelper; +}; + +#endif //__XN_SENSOR_STREAM_HELPER_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnStreamProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnStreamProcessor.cpp new file mode 100644 index 0000000..650eaa9 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnStreamProcessor.cpp @@ -0,0 +1,38 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnStreamProcessor.h" +#include +#include "XnSensor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStreamProcessor::XnStreamProcessor(XnDeviceStream* pStream, XnSensorStreamHelper* pHelper) : + XnDataProcessor(pHelper->GetPrivateData(), pStream->GetType()), + m_pStream(pStream), + m_pHelper(pHelper) +{ +} + + diff --git a/Source/Drivers/PS1080/Sensor/XnStreamProcessor.h b/Source/Drivers/PS1080/Sensor/XnStreamProcessor.h new file mode 100644 index 0000000..496c5f6 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnStreamProcessor.h @@ -0,0 +1,57 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_STREAM_PROCESSOR_H__ +#define __XN_STREAM_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDataProcessor.h" +#include "XnSensorStreamHelper.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +/** +* Base class for streams data processors +*/ +class XnStreamProcessor : public XnDataProcessor +{ +public: + XnStreamProcessor(XnDeviceStream* pStream, XnSensorStreamHelper* pHelper); + +//--------------------------------------------------------------------------- +// Utility Functions +//--------------------------------------------------------------------------- +protected: + XnDeviceStream* GetStream() { return m_pStream; } + XnSensorStreamHelper* GetStreamHelper() { return m_pHelper; } + +//--------------------------------------------------------------------------- +// Members +//--------------------------------------------------------------------------- +private: + XnDeviceStream* m_pStream; + XnSensorStreamHelper* m_pHelper; +}; + +#endif //__XN_STREAM_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.cpp new file mode 100644 index 0000000..ea85b44 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.cpp @@ -0,0 +1,60 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnTecDebugProcessor.h" +#include "XnSensor.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define XN_SENSOR_TEC_DEBUG_MAX_PACKET_SIZE 256 + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnTecDebugProcessor::XnTecDebugProcessor(XnDevicePrivateData* pDevicePrivateData) : + XnWholePacketProcessor(pDevicePrivateData, "TecDebug", XN_SENSOR_TEC_DEBUG_MAX_PACKET_SIZE), + m_Dump(NULL) +{ +} + +XnTecDebugProcessor::~XnTecDebugProcessor() +{ + xnDumpFileClose(m_Dump); +} + +void XnTecDebugProcessor::ProcessWholePacket(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData) +{ + if (m_Dump == NULL) + { + m_Dump = xnDumpFileOpenEx("TecDebug", TRUE, TRUE, "TecDebug.csv"); + } + + xnDumpFileWriteString(m_Dump, "%S\n", (XnChar*)pData); + + if (m_pDevicePrivateData->pSensor->IsTecDebugPring()) + { + printf("%S\n", (XnWChar*)pData); + } +} + diff --git a/Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.h b/Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.h new file mode 100644 index 0000000..27a47e6 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnTecDebugProcessor.h @@ -0,0 +1,52 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_TEC_DEBUG_PROCESSOR_H__ +#define __XN_TEC_DEBUG_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnWholePacketProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnTecDebugProcessor : public XnWholePacketProcessor +{ +public: + XnTecDebugProcessor(XnDevicePrivateData* pDevicePrivateData); + ~XnTecDebugProcessor(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnDumpFile* m_Dump; +}; + +#endif //__XN_TEC_DEBUG_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.cpp new file mode 100644 index 0000000..54662a5 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.cpp @@ -0,0 +1,108 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnUncompressedBayerProcessor.h" +#include "Uncomp.h" +#include "Bayer.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnUncompressedBayerProcessor::XnUncompressedBayerProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnUncompressedBayerProcessor::~XnUncompressedBayerProcessor() +{ +} + +XnStatus XnUncompressedBayerProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnImageProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_GRAY8: + break; + case ONI_PIXEL_FORMAT_RGB888: + XN_VALIDATE_BUFFER_ALLOCATE(m_UncompressedBayerBuffer, GetExpectedOutputSize()); + break; + default: + XN_LOG_WARNING_RETURN(XN_STATUS_ERROR, XN_MASK_SENSOR_PROTOCOL_IMAGE, "Unsupported image output format: %d", GetStream()->GetOutputFormat()); + } + + return (XN_STATUS_OK); +} + +void XnUncompressedBayerProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnUncompressedBayerProcessor::ProcessFramePacketChunk") + + // if output format is Gray8, we can write directly to output buffer. otherwise, we need + // to write to a temp buffer. + XnBuffer* pWriteBuffer = (GetStream()->GetOutputFormat() == ONI_PIXEL_FORMAT_GRAY8) ? GetWriteBuffer() : &m_UncompressedBayerBuffer; + + // make sure we have enough room + if (pWriteBuffer->GetFreeSpaceInBuffer() < nDataSize) + { + WriteBufferOverflowed(); + } + else + { + pWriteBuffer->UnsafeWrite(pData, nDataSize); + } + + XN_PROFILING_END_SECTION +} + +void XnUncompressedBayerProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XN_PROFILING_START_SECTION("XnUncompressedBayerProcessor::OnEndOfFrame") + + // if data was written to temp buffer, convert it now + switch (GetStream()->GetOutputFormat()) + { + case ONI_PIXEL_FORMAT_GRAY8: + break; + case ONI_PIXEL_FORMAT_RGB888: + { + Bayer2RGB888(m_UncompressedBayerBuffer.GetData(), GetWriteBuffer()->GetUnsafeWritePointer(), GetActualXRes(), GetActualYRes(), 1); + GetWriteBuffer()->UnsafeUpdateSize(GetActualXRes()*GetActualYRes()*3); + m_UncompressedBayerBuffer.Reset(); + } + break; + default: + assert(0); + return; + } + + XnImageProcessor::OnEndOfFrame(pHeader); + + XN_PROFILING_END_SECTION +} diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.h b/Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.h new file mode 100644 index 0000000..ac24c50 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedBayerProcessor.h @@ -0,0 +1,55 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_UNCOMPRESSED_BAYER_PROCESSOR_H__ +#define __XN_UNCOMPRESSED_BAYER_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnUncompressedBayerProcessor : public XnImageProcessor +{ +public: + XnUncompressedBayerProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnUncompressedBayerProcessor(); + + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnBuffer m_UncompressedBayerBuffer; +}; + +#endif //__XN_UNCOMPRESSED_BAYER_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.cpp new file mode 100644 index 0000000..ebbb045 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.cpp @@ -0,0 +1,78 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnUncompressedDepthProcessor.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnUncompressedDepthProcessor::XnUncompressedDepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnDepthProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnUncompressedDepthProcessor::~XnUncompressedDepthProcessor() +{ +} + +void XnUncompressedDepthProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnUncompressedDepthProcessor::ProcessFramePacketChunk") + + // when depth is uncompressed, we can just copy it directly to write buffer + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + // Check there is enough room for the depth pixels + if (CheckWriteBufferForOverflow(nDataSize)) + { + // sometimes, when packets are lost, we get uneven number of bytes, so we need to complete + // one byte, in order to keep UINT16 alignment + if (nDataSize % 2 != 0) + { + nDataSize--; + pData++; + } + + // copy values. Make sure we do not get corrupted shifts + XnUInt16* pRaw = (XnUInt16*)(pData); + XnUInt16* pRawEnd = (XnUInt16*)(pData + nDataSize); + OniDepthPixel* pDepthBuf = (OniDepthPixel*)pWriteBuffer->GetUnsafeWritePointer(); + + XnUInt16 shift; + while (pRaw < pRawEnd) + { + shift = (((*pRaw) < (XN_DEVICE_SENSOR_MAX_SHIFT_VALUE-1)) ? (*pRaw) : 0); + *pDepthBuf = GetOutput(shift); + + ++pRaw; + ++pDepthBuf; + } + + pWriteBuffer->UnsafeUpdateSize(nDataSize); + } + + XN_PROFILING_END_SECTION +} + diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.h b/Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.h new file mode 100644 index 0000000..6d0dfc6 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedDepthProcessor.h @@ -0,0 +1,46 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_UNCOMPRESSED_DEPTH_PROCESSOR_H__ +#define __XN_UNCOMPRESSED_DEPTH_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDepthProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnUncompressedDepthProcessor : public XnDepthProcessor +{ +public: + XnUncompressedDepthProcessor(XnSensorDepthStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + virtual ~XnUncompressedDepthProcessor(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); +}; + +#endif //__XN_UNCOMPRESSED_DEPTH_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.cpp new file mode 100644 index 0000000..7c8fca3 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.cpp @@ -0,0 +1,113 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnUncompressedYUV422toRGBImageProcessor.h" +#include "YUV.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnUncompressedYUV422toRGBImageProcessor::XnUncompressedYUV422toRGBImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnUncompressedYUV422toRGBImageProcessor::~XnUncompressedYUV422toRGBImageProcessor() +{ +} + +XnStatus XnUncompressedYUV422toRGBImageProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnImageProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_ContinuousBuffer, XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE) + + return (XN_STATUS_OK); +} + +void XnUncompressedYUV422toRGBImageProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnUncompressedYUV422toRGBImageProcessor::ProcessFramePacketChunk") + + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + if (m_ContinuousBuffer.GetSize() != 0) + { + // fill in to a whole element + XnUInt32 nReadBytes = XN_MIN(nDataSize, XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE - m_ContinuousBuffer.GetSize()); + m_ContinuousBuffer.UnsafeWrite(pData, nReadBytes); + pData += nReadBytes; + nDataSize -= nReadBytes; + + if (m_ContinuousBuffer.GetSize() == XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE) + { + if (CheckWriteBufferForOverflow(XN_YUV_TO_RGB_OUTPUT_ELEMENT_SIZE)) + { + // process it + XnUInt32 nActualRead = 0; + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + YUV422ToRGB888(m_ContinuousBuffer.GetData(), pWriteBuffer->GetUnsafeWritePointer(), XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE, &nActualRead, &nOutputSize); + pWriteBuffer->UnsafeUpdateSize(XN_YUV_TO_RGB_OUTPUT_ELEMENT_SIZE); + } + + m_ContinuousBuffer.Reset(); + } + } + + if (CheckWriteBufferForOverflow(nDataSize / XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE * XN_YUV_TO_RGB_OUTPUT_ELEMENT_SIZE)) + { + XnUInt32 nActualRead = 0; + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + YUV422ToRGB888(pData, pWriteBuffer->GetUnsafeWritePointer(), nDataSize, &nActualRead, &nOutputSize); + pWriteBuffer->UnsafeUpdateSize(nOutputSize); + pData += nActualRead; + nDataSize -= nActualRead; + + // if we have any bytes left, store them for next packet. + if (nDataSize > 0) + { + // no need to check for overflow. there can not be a case in which more than XN_INPUT_ELEMENT_SIZE + // are left. + m_ContinuousBuffer.UnsafeWrite(pData, nDataSize); + } + } + + XN_PROFILING_END_SECTION +} + +void XnUncompressedYUV422toRGBImageProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnImageProcessor::OnStartOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} + +void XnUncompressedYUV422toRGBImageProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnImageProcessor::OnEndOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.h new file mode 100644 index 0000000..8a04da0 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedYUV422toRGBImageProcessor.h @@ -0,0 +1,53 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_UNCOMPRESSED_YUV422_TO_RGB_IMAGE_PROCESSOR_H__ +#define __XN_UNCOMPRESSED_YUV422_TO_RGB_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnUncompressedYUV422toRGBImageProcessor : public XnImageProcessor +{ +public: + XnUncompressedYUV422toRGBImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnUncompressedYUV422toRGBImageProcessor(); + + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + +private: + XnBuffer m_ContinuousBuffer; +}; + +#endif //__XN_UNCOMPRESSED_YUV422_TO_RGB_IMAGE_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.cpp new file mode 100644 index 0000000..84706c4 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.cpp @@ -0,0 +1,113 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnUncompressedYUYVtoRGBImageProcessor.h" +#include "YUV.h" +#include + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnUncompressedYUYVtoRGBImageProcessor::XnUncompressedYUYVtoRGBImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager) : + XnImageProcessor(pStream, pHelper, pBufferManager) +{ +} + +XnUncompressedYUYVtoRGBImageProcessor::~XnUncompressedYUYVtoRGBImageProcessor() +{ +} + +XnStatus XnUncompressedYUYVtoRGBImageProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnImageProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + XN_VALIDATE_BUFFER_ALLOCATE(m_ContinuousBuffer, XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE) + + return (XN_STATUS_OK); +} + +void XnUncompressedYUYVtoRGBImageProcessor::ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* /*pHeader*/, const XnUChar* pData, XnUInt32 /*nDataOffset*/, XnUInt32 nDataSize) +{ + XN_PROFILING_START_SECTION("XnUncompressedYUYVtoRGBImageProcessor::ProcessFramePacketChunk") + + XnBuffer* pWriteBuffer = GetWriteBuffer(); + + if (m_ContinuousBuffer.GetSize() != 0) + { + // fill in to a whole element + XnUInt32 nReadBytes = XN_MIN(nDataSize, XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE - m_ContinuousBuffer.GetSize()); + m_ContinuousBuffer.UnsafeWrite(pData, nReadBytes); + pData += nReadBytes; + nDataSize -= nReadBytes; + + if (m_ContinuousBuffer.GetSize() == XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE) + { + if (CheckWriteBufferForOverflow(XN_YUV_TO_RGB_OUTPUT_ELEMENT_SIZE)) + { + // process it + XnUInt32 nActualRead = 0; + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + YUYVToRGB888(m_ContinuousBuffer.GetData(), pWriteBuffer->GetUnsafeWritePointer(), XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE, &nActualRead, &nOutputSize); + pWriteBuffer->UnsafeUpdateSize(XN_YUV_TO_RGB_OUTPUT_ELEMENT_SIZE); + } + + m_ContinuousBuffer.Reset(); + } + } + + if (CheckWriteBufferForOverflow(nDataSize / XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE * XN_YUV_TO_RGB_OUTPUT_ELEMENT_SIZE)) + { + XnUInt32 nActualRead = 0; + XnUInt32 nOutputSize = pWriteBuffer->GetFreeSpaceInBuffer(); + YUYVToRGB888(pData, pWriteBuffer->GetUnsafeWritePointer(), nDataSize, &nActualRead, &nOutputSize); + pWriteBuffer->UnsafeUpdateSize(nOutputSize); + pData += nActualRead; + nDataSize -= nActualRead; + + // if we have any bytes left, store them for next packet. + if (nDataSize > 0) + { + // no need to check for overflow. there can not be a case in which more than XN_INPUT_ELEMENT_SIZE + // are left. + m_ContinuousBuffer.UnsafeWrite(pData, nDataSize); + } + } + + XN_PROFILING_END_SECTION +} + +void XnUncompressedYUYVtoRGBImageProcessor::OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnImageProcessor::OnStartOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} + +void XnUncompressedYUYVtoRGBImageProcessor::OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader) +{ + XnImageProcessor::OnEndOfFrame(pHeader); + m_ContinuousBuffer.Reset(); +} diff --git a/Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.h b/Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.h new file mode 100644 index 0000000..227cda6 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnUncompressedYUYVtoRGBImageProcessor.h @@ -0,0 +1,53 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_UNCOMPRESSED_YUYV_TO_RGB_IMAGE_PROCESSOR_H__ +#define __XN_UNCOMPRESSED_YUYV_TO_RGB_IMAGE_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnImageProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +class XnUncompressedYUYVtoRGBImageProcessor : public XnImageProcessor +{ +public: + XnUncompressedYUYVtoRGBImageProcessor(XnSensorImageStream* pStream, XnSensorStreamHelper* pHelper, XnFrameBufferManager* pBufferManager); + ~XnUncompressedYUYVtoRGBImageProcessor(); + + XnStatus Init(); + + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- +protected: + virtual void ProcessFramePacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + virtual void OnStartOfFrame(const XnSensorProtocolResponseHeader* pHeader); + virtual void OnEndOfFrame(const XnSensorProtocolResponseHeader* pHeader); + +private: + XnBuffer m_ContinuousBuffer; +}; + +#endif //__XN_UNCOMPRESSED_YUYV_TO_RGB_IMAGE_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.cpp new file mode 100644 index 0000000..3d62e9f --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.cpp @@ -0,0 +1,60 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnWavelengthCorrectionDebugProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +XnWavelengthCorrectionDebugProcessor::XnWavelengthCorrectionDebugProcessor(XnDevicePrivateData* pDevicePrivateData) : + XnWholePacketProcessor(pDevicePrivateData, "WavelengthCorrectionDebug", sizeof(XnWavelengthCorrectionDebugPacket)), + m_DumpTxt(NULL) +{ +} + +XnWavelengthCorrectionDebugProcessor::~XnWavelengthCorrectionDebugProcessor() +{ + xnDumpFileClose(m_DumpTxt); +} + +void XnWavelengthCorrectionDebugProcessor::ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData) +{ + m_DumpTxt = xnDumpFileOpenEx("WavelengthCorrectionDebug", TRUE, TRUE, "WavelengthCorrection.csv"); + xnDumpFileWriteString(m_DumpTxt, "HostTimestamp,PacketID,BLast,BCurrent,IsHop,CurrentSlidingWindow,CurrentHopsCount,IsTecCalibrated,WaitPeriod,IsWavelengthUnstable,BestHopsCount,BestSetPoint,BestStep,IsTotallyUnstable,ConfiguredTecSetPoint,CurrentStep\n"); + + XnWavelengthCorrectionDebugPacket* pPacket = (XnWavelengthCorrectionDebugPacket*)pData; + + XnUInt64 nTimestamp = 0; + xnOSGetHighResTimeStamp(&nTimestamp); + + xnDumpFileWriteString(m_DumpTxt, "%llu,%hu,%f,%f,%hu,%x,%hu,%hu,%u,%hu,%hu,%u,%d,%hu,%u,%d\n", + nTimestamp, pHeader->nPacketID, + pPacket->fBLast, pPacket->fBCurrent, pPacket->nIsHop, pPacket->nCurrentSlidingWindow, + pPacket->nCurrentHopsCount, pPacket->nIsTecCalibrated, pPacket->nWaitPeriod, + pPacket->nIsWavelengthUnstable, pPacket->BestConf.nBestHopsCount, pPacket->BestConf.nBestSetPoint, + pPacket->BestConf.nBestStep, pPacket->nIsTotallyUnstable, pPacket->nConfiguredTecSetPoint, + pPacket->nCurrentStep); +} + diff --git a/Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.h b/Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.h new file mode 100644 index 0000000..77dff9e --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnWavelengthCorrectionDebugProcessor.h @@ -0,0 +1,51 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_WAVELENGTH_CORRECTION_DEBUG_PROCESSOR_H__ +#define __XN_WAVELENGTH_CORRECTION_DEBUG_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnWholePacketProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +class XnWavelengthCorrectionDebugProcessor : public XnWholePacketProcessor +{ +public: + XnWavelengthCorrectionDebugProcessor(XnDevicePrivateData* pDevicePrivateData); + ~XnWavelengthCorrectionDebugProcessor(); + +protected: + //--------------------------------------------------------------------------- + // Overridden Functions + //--------------------------------------------------------------------------- + virtual void ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData); + + //--------------------------------------------------------------------------- + // Class Members + //--------------------------------------------------------------------------- +private: + XnDumpFile* m_DumpTxt; +}; + +#endif // __XN_WAVELENGTH_CORRECTION_DEBUG_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.cpp b/Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.cpp new file mode 100644 index 0000000..fb1caa2 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.cpp @@ -0,0 +1,77 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnWholePacketProcessor.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnWholePacketProcessor::XnWholePacketProcessor(XnDevicePrivateData* pDevicePrivateData, const XnChar* csName, XnUInt32 nMaxPacketSize) : + XnDataProcessor(pDevicePrivateData, csName), + m_nMaxPacketSize(nMaxPacketSize) +{} + +XnWholePacketProcessor::~XnWholePacketProcessor() +{} + +XnStatus XnWholePacketProcessor::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = XnDataProcessor::Init(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = m_WholePacket.Allocate(m_nMaxPacketSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void XnWholePacketProcessor::ProcessPacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize) +{ + if (nDataOffset == 0 && m_WholePacket.GetSize() != 0) + { + // previous packet was not received to its end + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "%s: Expected %d additional bytes in packet (got %d out of %d bytes)!", m_csName, pHeader->nBufSize - m_WholePacket.GetSize(), m_WholePacket.GetSize(), pHeader->nBufSize); + m_WholePacket.Reset(); + } + + // sanity check + if (pHeader->nBufSize > m_WholePacket.GetMaxSize()) + { + xnLogWarning(XN_MASK_SENSOR_PROTOCOL, "Got a packet which is bigger than max size! (%d > %d)", pHeader->nBufSize, m_WholePacket.GetMaxSize()); + } + else + { + // append data + m_WholePacket.UnsafeWrite(pData, nDataSize); + + // check if we have entire packet + if (m_WholePacket.GetSize() == pHeader->nBufSize) + { + // process it + ProcessWholePacket(pHeader, m_WholePacket.GetData()); + m_WholePacket.Reset(); + } + } +} diff --git a/Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.h b/Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.h new file mode 100644 index 0000000..cc3aad6 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/XnWholePacketProcessor.h @@ -0,0 +1,59 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __XN_WHOLE_PACKET_PROCESSOR_H__ +#define __XN_WHOLE_PACKET_PROCESSOR_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDataProcessor.h" + +//--------------------------------------------------------------------------- +// XnWholePacketProcessor Class +//--------------------------------------------------------------------------- +class XnWholePacketProcessor : public XnDataProcessor +{ +public: + XnWholePacketProcessor(XnDevicePrivateData* pDevicePrivateData, const XnChar* csName, XnUInt32 nMaxPacketSize); + ~XnWholePacketProcessor(); + + XnStatus Init(); + +//--------------------------------------------------------------------------- +// Overridden Functions +//--------------------------------------------------------------------------- +protected: + void ProcessPacketChunk(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData, XnUInt32 nDataOffset, XnUInt32 nDataSize); + +//--------------------------------------------------------------------------- +// Virtual Functions +//--------------------------------------------------------------------------- +protected: + virtual void ProcessWholePacket(const XnSensorProtocolResponseHeader* pHeader, const XnUChar* pData) = 0; + +private: + /* The maximum size of the packet. */ + XnUInt32 m_nMaxPacketSize; + /* A buffer to store whole packet */ + XnBuffer m_WholePacket; +}; + +#endif //__XN_WHOLE_PACKET_PROCESSOR_H__ diff --git a/Source/Drivers/PS1080/Sensor/YUV.cpp b/Source/Drivers/PS1080/Sensor/YUV.cpp new file mode 100644 index 0000000..5a55f45 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/YUV.cpp @@ -0,0 +1,362 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "YUV.h" +#include + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + #ifdef __INTEL_COMPILER + #include + #else + #include + #endif +#endif + +//--------------------------------------------------------------------------- +// Global Variables +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +void YUV444ToRGB888(XnUInt8 cY, XnUInt8 cU, XnUInt8 cV, + XnUInt8& cR, XnUInt8& cG, XnUInt8& cB) +{ + XnInt32 nC = cY - 16; + XnInt16 nD = cU - 128; + XnInt16 nE = cV - 128; + + nC = nC * 298 + 128; + + cR = (XnUInt8)XN_MIN(XN_MAX((nC + 409 * nE) >> 8, 0), 255); + cG = (XnUInt8)XN_MIN(XN_MAX((nC - 100 * nD - 208 * nE) >> 8, 0), 255); + cB = (XnUInt8)XN_MIN(XN_MAX((nC + 516 * nD ) >> 8, 0), 255); +} + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + +void YUV422ToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32* pnActualRead, XnUInt32* pnRGBSize) +{ + const XnUInt8* pYUVLast = pYUVImage + nYUVSize - 8; + const XnUInt8* pYUVOrig = pYUVImage; + const XnUInt8* pRGBOrig = pRGBImage; + const XnUInt8* pRGBLast = pRGBImage + *pnRGBSize - 12; + + const __m128 minus128 = _mm_set_ps1(-128); + const __m128 plus113983 = _mm_set_ps1(1.13983F); + const __m128 minus039466 = _mm_set_ps1(-0.39466F); + const __m128 minus058060 = _mm_set_ps1(-0.58060F); + const __m128 plus203211 = _mm_set_ps1(2.03211F); + const __m128 zero = _mm_set_ps1(0); + const __m128 plus255 = _mm_set_ps1(255); + + // define YUV floats + __m128 y; + __m128 u; + __m128 v; + + __m128 temp; + + // define RGB floats + __m128 r; + __m128 g; + __m128 b; + + // define RGB integers + __m128i iR; + __m128i iG; + __m128i iB; + + XnUInt32* piR = (XnUInt32*)&iR; + XnUInt32* piG = (XnUInt32*)&iG; + XnUInt32* piB = (XnUInt32*)&iB; + + while (pYUVImage <= pYUVLast && pRGBImage <= pRGBLast) + { + // process 4 pixels at once (values should be ordered backwards) + y = _mm_set_ps(pYUVImage[YUV422_Y2 + YUV422_BPP], pYUVImage[YUV422_Y1 + YUV422_BPP], pYUVImage[YUV422_Y2], pYUVImage[YUV422_Y1]); + u = _mm_set_ps(pYUVImage[YUV422_U + YUV422_BPP], pYUVImage[YUV422_U + YUV422_BPP], pYUVImage[YUV422_U], pYUVImage[YUV422_U]); + v = _mm_set_ps(pYUVImage[YUV422_V + YUV422_BPP], pYUVImage[YUV422_V + YUV422_BPP], pYUVImage[YUV422_V], pYUVImage[YUV422_V]); + + u = _mm_add_ps(u, minus128); // u -= 128 + v = _mm_add_ps(v, minus128); // v -= 128 + + /* + + http://en.wikipedia.org/wiki/YUV + + From YUV to RGB: + R = Y + 1.13983 V + G = Y - 0.39466 U - 0.58060 V + B = Y + 2.03211 U + + */ + + temp = _mm_mul_ps(plus113983, v); + r = _mm_add_ps(y, temp); + + temp = _mm_mul_ps(minus039466, u); + g = _mm_add_ps(y, temp); + temp = _mm_mul_ps(minus058060, v); + g = _mm_add_ps(g, temp); + + temp = _mm_mul_ps(plus203211, u); + b = _mm_add_ps(y, temp); + + // make sure no value is smaller than 0 + r = _mm_max_ps(r, zero); + g = _mm_max_ps(g, zero); + b = _mm_max_ps(b, zero); + + // make sure no value is bigger than 255 + r = _mm_min_ps(r, plus255); + g = _mm_min_ps(g, plus255); + b = _mm_min_ps(b, plus255); + + // convert floats to int16 (there is no conversion to uint8, just to int8). + iR = _mm_cvtps_epi32(r); + iG = _mm_cvtps_epi32(g); + iB = _mm_cvtps_epi32(b); + + // extract the 4 pixels RGB values. + // because we made sure values are between 0 and 255, we can just take the lower byte + // of each INT16 + pRGBImage[0] = (XnUInt8)piR[0]; + pRGBImage[1] = (XnUInt8)piG[0]; + pRGBImage[2] = (XnUInt8)piB[0]; + + pRGBImage[3] = (XnUInt8)piR[1]; + pRGBImage[4] = (XnUInt8)piG[1]; + pRGBImage[5] = (XnUInt8)piB[1]; + + pRGBImage[6] = (XnUInt8)piR[2]; + pRGBImage[7] = (XnUInt8)piG[2]; + pRGBImage[8] = (XnUInt8)piB[2]; + + pRGBImage[9] = (XnUInt8)piR[3]; + pRGBImage[10] = (XnUInt8)piG[3]; + pRGBImage[11] = (XnUInt8)piB[3]; + + // advance the streams + pYUVImage += 8; + pRGBImage += 12; + } + + *pnActualRead = (XnUInt32)(pYUVImage - pYUVOrig); + *pnRGBSize = (XnUInt32)(pRGBImage - pRGBOrig); +} + +#else // not Win32 + +void YUV422ToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32* pnActualRead, XnUInt32* pnRGBSize) +{ + const XnUInt8* pOrigYUV = pYUVImage; + const XnUInt8* pCurrYUV = pYUVImage; + const XnUInt8* pOrigRGB = pRGBImage; + XnUInt8* pCurrRGB = pRGBImage; + const XnUInt8* pLastYUV = pYUVImage + nYUVSize - YUV422_BPP; + const XnUInt8* pLastRGB = pRGBImage + *pnRGBSize - YUV_RGB_BPP; + + while (pCurrYUV <= pLastYUV && pCurrRGB <= pLastRGB) + { + YUV444ToRGB888(pCurrYUV[YUV422_Y1], pCurrYUV[YUV422_U], pCurrYUV[YUV422_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE]); + pCurrRGB += YUV_RGB_BPP; + YUV444ToRGB888(pCurrYUV[YUV422_Y2], pCurrYUV[YUV422_U], pCurrYUV[YUV422_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE]); + pCurrRGB += YUV_RGB_BPP; + pCurrYUV += YUV422_BPP; + } + + *pnActualRead = pCurrYUV - pOrigYUV; + *pnRGBSize = pCurrRGB - pOrigRGB; +} + +#endif + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + +void YUYVToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32* pnActualRead, XnUInt32* pnRGBSize) +{ + const XnUInt8* pYUVLast = pYUVImage + nYUVSize - 8; + const XnUInt8* pYUVOrig = pYUVImage; + const XnUInt8* pRGBOrig = pRGBImage; + const XnUInt8* pRGBLast = pRGBImage + *pnRGBSize - 12; + + const __m128 minus128 = _mm_set_ps1(-128); + const __m128 plus113983 = _mm_set_ps1(1.13983F); + const __m128 minus039466 = _mm_set_ps1(-0.39466F); + const __m128 minus058060 = _mm_set_ps1(-0.58060F); + const __m128 plus203211 = _mm_set_ps1(2.03211F); + const __m128 zero = _mm_set_ps1(0); + const __m128 plus255 = _mm_set_ps1(255); + + // define YUV floats + __m128 y; + __m128 u; + __m128 v; + + __m128 temp; + + // define RGB floats + __m128 r; + __m128 g; + __m128 b; + + // define RGB integers + __m128i iR; + __m128i iG; + __m128i iB; + + XnUInt32* piR = (XnUInt32*)&iR; + XnUInt32* piG = (XnUInt32*)&iG; + XnUInt32* piB = (XnUInt32*)&iB; + + while (pYUVImage <= pYUVLast && pRGBImage <= pRGBLast) + { + // process 4 pixels at once (values should be ordered backwards) + y = _mm_set_ps(pYUVImage[YUYV_Y2 + YUYV_BPP], pYUVImage[YUYV_Y1 + YUYV_BPP], pYUVImage[YUYV_Y2], pYUVImage[YUYV_Y1]); + u = _mm_set_ps(pYUVImage[YUYV_U + YUYV_BPP], pYUVImage[YUYV_U + YUYV_BPP], pYUVImage[YUYV_U], pYUVImage[YUYV_U]); + v = _mm_set_ps(pYUVImage[YUYV_V + YUYV_BPP], pYUVImage[YUYV_V + YUYV_BPP], pYUVImage[YUYV_V], pYUVImage[YUYV_V]); + + u = _mm_add_ps(u, minus128); // u -= 128 + v = _mm_add_ps(v, minus128); // v -= 128 + + /* + + http://en.wikipedia.org/wiki/YUV + + From YUV to RGB: + R = Y + 1.13983 V + G = Y - 0.39466 U - 0.58060 V + B = Y + 2.03211 U + + */ + + temp = _mm_mul_ps(plus113983, v); + r = _mm_add_ps(y, temp); + + temp = _mm_mul_ps(minus039466, u); + g = _mm_add_ps(y, temp); + temp = _mm_mul_ps(minus058060, v); + g = _mm_add_ps(g, temp); + + temp = _mm_mul_ps(plus203211, u); + b = _mm_add_ps(y, temp); + + // make sure no value is smaller than 0 + r = _mm_max_ps(r, zero); + g = _mm_max_ps(g, zero); + b = _mm_max_ps(b, zero); + + // make sure no value is bigger than 255 + r = _mm_min_ps(r, plus255); + g = _mm_min_ps(g, plus255); + b = _mm_min_ps(b, plus255); + + // convert floats to int16 (there is no conversion to uint8, just to int8). + iR = _mm_cvtps_epi32(r); + iG = _mm_cvtps_epi32(g); + iB = _mm_cvtps_epi32(b); + + // extract the 4 pixels RGB values. + // because we made sure values are between 0 and 255, we can just take the lower byte + // of each INT16 + pRGBImage[0] = (XnUInt8)piR[0]; + pRGBImage[1] = (XnUInt8)piG[0]; + pRGBImage[2] = (XnUInt8)piB[0]; + + pRGBImage[3] = (XnUInt8)piR[1]; + pRGBImage[4] = (XnUInt8)piG[1]; + pRGBImage[5] = (XnUInt8)piB[1]; + + pRGBImage[6] = (XnUInt8)piR[2]; + pRGBImage[7] = (XnUInt8)piG[2]; + pRGBImage[8] = (XnUInt8)piB[2]; + + pRGBImage[9] = (XnUInt8)piR[3]; + pRGBImage[10] = (XnUInt8)piG[3]; + pRGBImage[11] = (XnUInt8)piB[3]; + + // advance the streams + pYUVImage += 8; + pRGBImage += 12; + } + + *pnActualRead = (XnUInt32)(pYUVImage - pYUVOrig); + *pnRGBSize = (XnUInt32)(pRGBImage - pRGBOrig); +} + +#else // not Win32 + +void YUYVToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32* pnActualRead, XnUInt32* pnRGBSize) +{ + const XnUInt8* pOrigYUV = pYUVImage; + const XnUInt8* pCurrYUV = pYUVImage; + const XnUInt8* pOrigRGB = pRGBImage; + XnUInt8* pCurrRGB = pRGBImage; + const XnUInt8* pLastYUV = pYUVImage + nYUVSize - YUYV_BPP; + const XnUInt8* pLastRGB = pRGBImage + *pnRGBSize - YUV_RGB_BPP; + + while (pCurrYUV <= pLastYUV && pCurrRGB <= pLastRGB) + { + YUV444ToRGB888(pCurrYUV[YUYV_Y1], pCurrYUV[YUYV_U], pCurrYUV[YUYV_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE]); + pCurrRGB += YUV_RGB_BPP; + YUV444ToRGB888(pCurrYUV[YUYV_Y2], pCurrYUV[YUYV_U], pCurrYUV[YUYV_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE]); + pCurrRGB += YUV_RGB_BPP; + pCurrYUV += YUYV_BPP; + } + + *pnActualRead = pCurrYUV - pOrigYUV; + *pnRGBSize = pCurrRGB - pOrigRGB; +} + +#endif + +void YUV420ToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32 /*nRGBSize*/) +{ + const XnUInt8* pLastYUV = pYUVImage + nYUVSize - YUV420_BPP; + + while (pYUVImage < pLastYUV && pRGBImage < pYUVImage) + { + YUV444ToRGB888(pYUVImage[YUV420_Y1], pYUVImage[YUV420_U], pYUVImage[YUV420_V], + pRGBImage[YUV_RED], pRGBImage[YUV_GREEN], pRGBImage[YUV_BLUE]); + pRGBImage += YUV_RGB_BPP; + + YUV444ToRGB888(pYUVImage[YUV420_Y2], pYUVImage[YUV420_U], pYUVImage[YUV420_V], + pRGBImage[YUV_RED], pRGBImage[YUV_GREEN], pRGBImage[YUV_BLUE]); + pRGBImage += YUV_RGB_BPP; + + YUV444ToRGB888(pYUVImage[YUV420_Y3], pYUVImage[YUV420_U], pYUVImage[YUV420_V], + pRGBImage[YUV_RED], pRGBImage[YUV_GREEN], pRGBImage[YUV_BLUE]); + pRGBImage += YUV_RGB_BPP; + + YUV444ToRGB888(pYUVImage[YUV420_Y4], pYUVImage[YUV420_U], pYUVImage[YUV420_V], + pRGBImage[YUV_RED], pRGBImage[YUV_GREEN], pRGBImage[YUV_BLUE]); + pRGBImage += YUV_RGB_BPP; + + pYUVImage += YUV420_BPP; + } +} diff --git a/Source/Drivers/PS1080/Sensor/YUV.h b/Source/Drivers/PS1080/Sensor/YUV.h new file mode 100644 index 0000000..413b7c3 --- /dev/null +++ b/Source/Drivers/PS1080/Sensor/YUV.h @@ -0,0 +1,69 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_YUV_H_ +#define _XN_YUV_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "XnDeviceSensor.h" + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define YUV422_U 0 +#define YUV422_Y1 1 +#define YUV422_V 2 +#define YUV422_Y2 3 +#define YUV422_BPP 4 + +#define YUYV_Y1 0 +#define YUYV_U 1 +#define YUYV_Y2 2 +#define YUYV_V 3 +#define YUYV_BPP 4 + +#define YUV420_U 0 +#define YUV420_Y1 1 +#define YUV420_Y2 2 +#define YUV420_V 3 +#define YUV420_Y3 4 +#define YUV420_Y4 5 +#define YUV420_BPP 6 + +#define YUV_RED 0 +#define YUV_GREEN 1 +#define YUV_BLUE 2 +#define YUV_RGB_BPP 3 + +/* The size of an input element in the stream. */ +#define XN_YUV_TO_RGB_INPUT_ELEMENT_SIZE 8 +/* The size of an output element in the stream. */ +#define XN_YUV_TO_RGB_OUTPUT_ELEMENT_SIZE 12 + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +void YUV422ToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32* pnActualRead, XnUInt32* pnRGBSize); +void YUYVToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32* pnActualRead, XnUInt32* pnRGBSize); +void YUV420ToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32 nRGBSize); + +#endif //_XN_BAYER_H_ diff --git a/Source/Drivers/PS1080/XnPsVersion.h b/Source/Drivers/PS1080/XnPsVersion.h new file mode 100644 index 0000000..78dbc75 --- /dev/null +++ b/Source/Drivers/PS1080/XnPsVersion.h @@ -0,0 +1,57 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _XN_PS_VERSION_H_ +#define _XN_PS_VERSION_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +/** Xiron major version. */ +#define XN_PS_MAJOR_VERSION 5 +/** Xiron minor version. */ +#define XN_PS_MINOR_VERSION 1 +/** Xiron maintenance version. */ +#define XN_PS_MAINTENANCE_VERSION 4 +/** Xiron build version. */ +#define XN_PS_BUILD_VERSION 1 + +/** Xiron version (in brief string format): "Major.Minor.Maintenance (Build)" */ +#define XN_PS_BRIEF_VERSION_STRING \ + XN_STRINGIFY(XN_PS_MAJOR_VERSION) "." \ + XN_STRINGIFY(XN_PS_MINOR_VERSION) "." \ + XN_STRINGIFY(XN_PS_MAINTENANCE_VERSION) \ + " (Build " XN_STRINGIFY(XN_PS_BUILD_VERSION) ")" + +/** Xiron version (in numeric format): (Xiron major version * 100000000 + Xiron minor version * 1000000 + Xiron maintenance version * 10000 + Xiron build version). */ +#define XN_PS_VERSION (XN_PS_MAJOR_VERSION*100000000 + XN_PS_MINOR_VERSION*1000000 + XN_PS_MAINTENANCE_VERSION*10000 + XN_PS_BUILD_VERSION) + +/** Xiron version (in string format): "Major.Minor.Maintenance.Build-Platform (MMM DD YYYY HH:MM:SS)". */ +#define XN_PS_VERSION_STRING \ + XN_PS_BRIEF_VERSION_STRING "-" \ + XN_PLATFORM_STRING " (" XN_TIMESTAMP ")" + +#endif //_XN_VERSION_H_ + diff --git a/Source/Drivers/PSLink/Android.mk b/Source/Drivers/PSLink/Android.mk new file mode 100644 index 0000000..7c78057 --- /dev/null +++ b/Source/Drivers/PSLink/Android.mk @@ -0,0 +1,41 @@ + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# set path to source +MY_PREFIX := $(LOCAL_PATH) + +# list all source files +MY_SRC_FILES := \ + $(MY_PREFIX)/*.cpp \ + $(MY_PREFIX)/LinkProtoLib/*.cpp \ + $(MY_PREFIX)/DriverImpl/*.cpp + +# expand the wildcards +MY_SRC_FILE_EXPANDED := $(wildcard $(MY_SRC_FILES)) + +# make those paths relative to here +LOCAL_SRC_FILES := $(MY_SRC_FILE_EXPANDED:$(LOCAL_PATH)/%=%) + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/../../../Include \ + $(LOCAL_PATH)/../../../ThirdParty/PSCommon/XnLib/Include \ + $(LOCAL_PATH) \ + $(LOCAL_PATH)/Protocols/XnLinkProto \ + $(LOCAL_PATH)/LinkProtoLib \ + +LOCAL_CFLAGS:= -fvisibility=hidden -DXN_CORE_EXPORTS + +LOCAL_LDFLAGS += -Wl,--export-dynamic + +LOCAL_STATIC_LIBRARIES := XnLib +LOCAL_SHARED_LIBRARIES := liblog libdl libusb libgabi++ +LOCAL_PREBUILT_LIBS := libc + +ifndef OPENNI2_ANDROID_OS_BUILD + LOCAL_LDLIBS += -llog +endif + +LOCAL_MODULE := PSLink + +include $(BUILD_SHARED_LIBRARY) diff --git a/Source/Drivers/PSLink/DriverImpl/LinkExportedOniDriver.cpp b/Source/Drivers/PSLink/DriverImpl/LinkExportedOniDriver.cpp new file mode 100644 index 0000000..fb7f5ee --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkExportedOniDriver.cpp @@ -0,0 +1,32 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniDriver.h" + +//--------------------------------------------------------------------------- +// LinkOniDriver EXPORT +//--------------------------------------------------------------------------- +ONI_EXPORT_DRIVER(LinkOniDriver); + +// The following line is needed to be once in *ALL* of the high level shared library modules. DO NOT REMOVE!!! +XN_API_EXPORT_INIT() diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.cpp b/Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.cpp new file mode 100644 index 0000000..45abe6e --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.cpp @@ -0,0 +1,229 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniDepthStream.h" + +//--------------------------------------------------------------------------- +// LinkOniDepthStream class +//--------------------------------------------------------------------------- +#define XN_MASK_LINK_STREAM "LinkDepthStream" + +LinkOniDepthStream::LinkOniDepthStream(const char* configFile, xn::PrimeClient* pSensor, LinkOniDevice* pDevice) : + LinkOniMapStream(configFile, "Depth", pSensor, ONI_SENSOR_DEPTH, pDevice) +{ +} + +OniStatus LinkOniDepthStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + const XnShiftToDepthTables* pTables = NULL; + XnStatus nRetVal; + XnFloat fValue = 0; + XnUInt32 nTableSize; + + switch (propertyId) + { + // int props + case ONI_STREAM_PROPERTY_MIN_VALUE: + ENSURE_PROP_SIZE(*pDataSize, int); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, 0); + break; + + case ONI_STREAM_PROPERTY_MAX_VALUE: + ENSURE_PROP_SIZE(*pDataSize, int); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().nDeviceMaxDepthValue); + break; + + case LINK_PROP_MAX_SHIFT: + ENSURE_PROP_SIZE(*pDataSize, int); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().nDeviceMaxShiftValue); + break; + + case LINK_PROP_ZERO_PLANE_DISTANCE: + ENSURE_PROP_SIZE(*pDataSize, OniDepthPixel); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().nZeroPlaneDistance); + break; + + case LINK_PROP_CONST_SHIFT: + ENSURE_PROP_SIZE(*pDataSize, int); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().nConstShift); + break; + + case LINK_PROP_PARAM_COEFF: + ENSURE_PROP_SIZE(*pDataSize, int); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().nParamCoeff); + break; + + case LINK_PROP_SHIFT_SCALE: + ENSURE_PROP_SIZE(*pDataSize, int); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().nShiftScale); + break; + + // real props + //TODO: consider moving these two to MapStream + case ONI_STREAM_PROPERTY_HORIZONTAL_FOV: + ENSURE_PROP_SIZE(*pDataSize, XnFloat); + m_pInputStream->GetFieldOfView(&fValue, NULL); + ASSIGN_PROP_VALUE_FLOAT(data, *pDataSize, fValue); + break; + + case ONI_STREAM_PROPERTY_VERTICAL_FOV: + ENSURE_PROP_SIZE(*pDataSize, XnFloat); + m_pInputStream->GetFieldOfView(NULL, &fValue); + ASSIGN_PROP_VALUE_FLOAT(data, *pDataSize, fValue); + break; + + case LINK_PROP_ZERO_PLANE_PIXEL_SIZE: + ENSURE_PROP_SIZE(*pDataSize, XnFloat); + ASSIGN_PROP_VALUE_FLOAT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().fZeroPlanePixelSize); + break; + + case LINK_PROP_ZERO_PLANE_OUTPUT_PIXEL_SIZE: + ENSURE_PROP_SIZE(*pDataSize, XnDouble); + ASSIGN_PROP_VALUE_FLOAT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().nZeroPlaneDistance / m_pInputStream->GetCameraIntrinsics().m_fEffectiveFocalLengthInPixels); + break; + + case LINK_PROP_EMITTER_DEPTH_CMOS_DISTANCE: + ENSURE_PROP_SIZE(*pDataSize, XnFloat); + ASSIGN_PROP_VALUE_FLOAT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().fEmitterDCmosDistance); + break; + + case LINK_PROP_DEPTH_SCALE: + ENSURE_PROP_SIZE(*pDataSize, XnDouble); + ASSIGN_PROP_VALUE_FLOAT(data, *pDataSize, m_pInputStream->GetShiftToDepthConfig().dDepthScale); + break; + + // tables + case LINK_PROP_SHIFT_TO_DEPTH_TABLE: + nRetVal = m_pInputStream->GetShiftToDepthTables(pTables); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + + nTableSize = pTables->nShiftsCount * sizeof(OniDepthPixel); + if (*pDataSize < (int)nTableSize) + { + xnLogError(XN_MASK_LINK_STREAM, "Get S2D table - buffer too small (expected %d, got %d)", nTableSize, *pDataSize); + return ONI_STATUS_BAD_PARAMETER; + } + + xnOSMemCopy(data, pTables->pShiftToDepthTable, nTableSize); + break; + + case LINK_PROP_DEPTH_TO_SHIFT_TABLE: + nRetVal = m_pInputStream->GetShiftToDepthTables(pTables); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + + nTableSize = pTables->nDepthsCount * sizeof(XnUInt16); + if (*pDataSize < (int)nTableSize) + { + xnLogError(XN_MASK_LINK_STREAM, "Get D2S table - buffer too small (expected %d, got %d)", nTableSize, *pDataSize); + return ONI_STATUS_BAD_PARAMETER; + } + + xnOSMemCopy(data, pTables->pDepthToShiftTable, nTableSize); + break; + + default: + return LinkOniMapStream::getProperty(propertyId, data, pDataSize); + } + + return ONI_STATUS_OK; +} + +OniBool LinkOniDepthStream::isPropertySupported(int propertyId) +{ + switch(propertyId) + { + // int props + case ONI_STREAM_PROPERTY_MIN_VALUE: + case ONI_STREAM_PROPERTY_MAX_VALUE: + case LINK_PROP_MAX_SHIFT: + case LINK_PROP_ZERO_PLANE_DISTANCE: + case LINK_PROP_CONST_SHIFT: + case LINK_PROP_PARAM_COEFF: + case LINK_PROP_SHIFT_SCALE: + // real props + //TODO: consider moving these two to MapStream + case ONI_STREAM_PROPERTY_HORIZONTAL_FOV: + case ONI_STREAM_PROPERTY_VERTICAL_FOV: + case LINK_PROP_ZERO_PLANE_PIXEL_SIZE: + case LINK_PROP_ZERO_PLANE_OUTPUT_PIXEL_SIZE: + case LINK_PROP_EMITTER_DEPTH_CMOS_DISTANCE: + case LINK_PROP_DEPTH_SCALE: + // tables + case LINK_PROP_SHIFT_TO_DEPTH_TABLE: + case LINK_PROP_DEPTH_TO_SHIFT_TABLE: + return true; + default: + return LinkOniMapStream::isPropertySupported(propertyId); + } +} + +void LinkOniDepthStream::notifyAllProperties() +{ + LinkOniMapStream::notifyAllProperties(); + + // int props + int nValue; + int size = sizeof(nValue); + + getProperty(LINK_PROP_MAX_SHIFT, &nValue, &size); + raisePropertyChanged(LINK_PROP_MAX_SHIFT, &nValue, size); + + getProperty(LINK_PROP_ZERO_PLANE_DISTANCE, &nValue, &size); + raisePropertyChanged(LINK_PROP_ZERO_PLANE_DISTANCE, &nValue, size); + + getProperty(LINK_PROP_CONST_SHIFT, &nValue, &size); + raisePropertyChanged(LINK_PROP_CONST_SHIFT, &nValue, size); + + getProperty(LINK_PROP_PARAM_COEFF, &nValue, &size); + raisePropertyChanged(LINK_PROP_PARAM_COEFF, &nValue, size); + + getProperty(LINK_PROP_SHIFT_SCALE, &nValue, &size); + raisePropertyChanged(LINK_PROP_SHIFT_SCALE, &nValue, size); + + // real props + XnDouble dValue; + size = sizeof(dValue); + //TODO: consider moving these two to MapStream + getProperty(ONI_STREAM_PROPERTY_VERTICAL_FOV, &dValue, &size); + raisePropertyChanged(ONI_STREAM_PROPERTY_VERTICAL_FOV, &dValue, size); + + getProperty(LINK_PROP_ZERO_PLANE_PIXEL_SIZE, &dValue, &size); + raisePropertyChanged(LINK_PROP_ZERO_PLANE_PIXEL_SIZE, &dValue, size); + + getProperty(LINK_PROP_ZERO_PLANE_OUTPUT_PIXEL_SIZE, &dValue, &size); + raisePropertyChanged(LINK_PROP_ZERO_PLANE_OUTPUT_PIXEL_SIZE, &dValue, size); + + getProperty(LINK_PROP_EMITTER_DEPTH_CMOS_DISTANCE, &dValue, &size); + raisePropertyChanged(LINK_PROP_EMITTER_DEPTH_CMOS_DISTANCE, &dValue, size); + + getProperty(LINK_PROP_DEPTH_SCALE, &dValue, &size); + raisePropertyChanged(LINK_PROP_DEPTH_SCALE, &dValue, size); + + // tables + const XnShiftToDepthTables* pTables = NULL; + m_pInputStream->GetShiftToDepthTables(pTables); + + raisePropertyChanged(LINK_PROP_SHIFT_TO_DEPTH_TABLE, pTables->pShiftToDepthTable, pTables->nShiftsCount * sizeof(OniDepthPixel)); + + raisePropertyChanged(LINK_PROP_DEPTH_TO_SHIFT_TABLE, pTables->pDepthToShiftTable, pTables->nDepthsCount * sizeof(XnUInt16)); +} diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.h b/Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.h new file mode 100644 index 0000000..61afb7e --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniDepthStream.h @@ -0,0 +1,42 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __LINK_ONI_DEPTH_STREAM_H__ +#define __LINK_ONI_DEPTH_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniMapStream.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class LinkOniDepthStream : + public LinkOniMapStream +{ +public: + LinkOniDepthStream(const char* configFile, xn::PrimeClient* pSensor, LinkOniDevice* pDevice); + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniBool isPropertySupported(int propertyId); + virtual void notifyAllProperties(); +}; + +#endif // __LINK_ONI_DEPTH_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniDevice.cpp b/Source/Drivers/PSLink/DriverImpl/LinkOniDevice.cpp new file mode 100644 index 0000000..87f7cd1 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniDevice.cpp @@ -0,0 +1,1323 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniDevice.h" +#include "LinkOniDriver.h" + +#include "LinkDeviceEnumeration.h" +#include +#include + +//--------------------------------------------------------------------------- +// LinkOniDevice class +//--------------------------------------------------------------------------- +#define XN_MASK_LINK_DEVICE "LinkDevice" +#define CONFIG_DEVICE_SECTION "Device" + +LinkOniDevice::LinkOniDevice(const char* configFile, const XnChar* uri, oni::driver::DriverServices& driverServices, LinkOniDriver* pDriver) : + m_configFile(configFile), m_pSensor(NULL), m_driverServices(driverServices), m_pDriver(pDriver) +{ + xnOSMemCopy(&m_info, LinkDeviceEnumeration::GetDeviceInfo(uri), sizeof(m_info)); +} + +LinkOniDevice::~LinkOniDevice() +{ + // free the allocated arrays + for(int i=0; i < m_numSensors; ++i) + { + XN_DELETE_ARR(m_sensors[i].pSupportedVideoModes); + } + + Destroy(); +} + +XnStatus LinkOniDevice::readSupportedModesFromStream(XnFwStreamInfo &info, xnl::Array &aSupportedModes) +{ + XnUInt16 streamId; + XnStatus nRetVal = m_pSensor->CreateInputStream(info.type, info.creationInfo, streamId); + XN_IS_STATUS_OK(nRetVal); + + // TODO: make sure this cast doesn't make us problems + xn::LinkFrameInputStream *pInputStream = (xn::LinkFrameInputStream *)m_pSensor->GetInputStream(streamId); + XN_VALIDATE_OUTPUT_PTR(pInputStream); + + aSupportedModes.CopyFrom(pInputStream->GetSupportedVideoModes()); + + m_pSensor->DestroyInputStream(streamId); + return XN_STATUS_OK; +} + +XnStatus LinkOniDevice::FillSupportedVideoModes() +{ + int nSupportedModes = 0; + xnl::Array aSupportedModes; + + xnl::Array aEnumerated; + + int s = -1; + int writeIndex; + + // Depth + m_pSensor->EnumerateStreams((XnStreamType)XN_LINK_STREAM_TYPE_SHIFTS, aEnumerated); + for (int c = 0; c < (int)aEnumerated.GetSize(); ++c) + { + XnStatus nRetVal = readSupportedModesFromStream(aEnumerated[c], aSupportedModes); + XN_IS_STATUS_OK(nRetVal); + + ++s; + m_sensors[s].sensorType = ONI_SENSOR_DEPTH; + m_sensors[s].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, aSupportedModes.GetSize()); + XN_VALIDATE_ALLOC_PTR(m_sensors[s].pSupportedVideoModes); + nSupportedModes = aSupportedModes.GetSize(); + + writeIndex = 0; + for(int i=0; i < nSupportedModes; ++i) + { + m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_sensors[s].pSupportedVideoModes[writeIndex].fps = aSupportedModes[i].m_nFPS; + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX = aSupportedModes[i].m_nXRes; + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY = aSupportedModes[i].m_nYRes; + + bool foundMatch = false; + for (int j = 0; j < writeIndex; ++j) + { + if (m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat == m_sensors[s].pSupportedVideoModes[j].pixelFormat && + m_sensors[s].pSupportedVideoModes[writeIndex].fps == m_sensors[s].pSupportedVideoModes[j].fps && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX == m_sensors[s].pSupportedVideoModes[j].resolutionX && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY == m_sensors[s].pSupportedVideoModes[j].resolutionY) + { + // Already know this configuration + foundMatch = true; + break; + } + } + if (!foundMatch) + { + ++writeIndex; + } + } + m_sensors[s].numSupportedVideoModes = writeIndex; + m_numSensors = s+1; + } + aEnumerated.Clear(); + + // IR + m_pSensor->EnumerateStreams((XnStreamType)XN_LINK_STREAM_TYPE_IR, aEnumerated); + for (int c = 0; c < (int)aEnumerated.GetSize(); ++c) + { + XnStatus nRetVal = readSupportedModesFromStream(aEnumerated[c], aSupportedModes); + XN_IS_STATUS_OK(nRetVal); + + ++s; + m_sensors[s].sensorType = ONI_SENSOR_IR; + m_sensors[s].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, aSupportedModes.GetSize()); + XN_VALIDATE_ALLOC_PTR(m_sensors[s].pSupportedVideoModes); + nSupportedModes = aSupportedModes.GetSize(); + + writeIndex = 0; + for(int i=0; i < nSupportedModes; ++i) + { + m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat = ONI_PIXEL_FORMAT_GRAY16; + m_sensors[s].pSupportedVideoModes[writeIndex].fps = aSupportedModes[i].m_nFPS; + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX = aSupportedModes[i].m_nXRes; + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY = aSupportedModes[i].m_nYRes; + + bool foundMatch = false; + for (int j = 0; j < writeIndex; ++j) + { + if (m_sensors[s].pSupportedVideoModes[writeIndex].pixelFormat == m_sensors[s].pSupportedVideoModes[j].pixelFormat && + m_sensors[s].pSupportedVideoModes[writeIndex].fps == m_sensors[s].pSupportedVideoModes[j].fps && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionX == m_sensors[s].pSupportedVideoModes[j].resolutionX && + m_sensors[s].pSupportedVideoModes[writeIndex].resolutionY == m_sensors[s].pSupportedVideoModes[j].resolutionY) + { + // Already know this configuration + foundMatch = true; + break; + } + } + if (!foundMatch) + { + ++writeIndex; + } + } + m_sensors[s].numSupportedVideoModes = writeIndex; + m_numSensors = s+1; + } + aEnumerated.Clear(); + +/* // Color + + // first, make sure that our sensor actually supports Image + XnUInt64 nImageSupported = FALSE; + XnStatus nRetVal = m_sensor.GetProperty(XN_MASK_DEVICE, XN_MODULE_PROPERTY_IMAGE_SUPPORTED, &nImageSupported); + XN_IS_STATUS_OK(nRetVal); + if (nImageSupported) + { + ++s; + nSupportedModes = m_sensor.GetDevicePrivateData()->FWInfo.imageModes.GetSize(); + pSupportedModes = m_sensor.GetDevicePrivateData()->FWInfo.imageModes.GetData(); + + m_sensors[s].sensorType = ONI_SENSOR_COLOR; + m_sensors[s].numSupportedVideoModes = 0; // to be changed later.. + m_sensors[s].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, nSupportedModes * 10); + XN_VALIDATE_ALLOC_PTR(m_sensors[s].pSupportedVideoModes); + + writeIndex = 0; + for(XnUInt32 j=0; j < nSupportedModes; ++j) + { + // make an OniVideoMode for each OniFormat supported by the input format + OniPixelFormat aOniFormats[10]; + int nOniFormats = 0; + LinkOniColorStream::GetAllowedOniOutputFormatForInputFormat((XnIOImageFormats)pSupportedModes[j].nFormat, aOniFormats, &nOniFormats); + for(int curOni=0; curOniInit(m_info.uri, XN_TRANSPORT_TYPE_USB); + if (retVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_LINK_DEVICE, "Failed to initialize prime client: %s", xnGetStatusString(retVal)); + XN_ASSERT(FALSE); + XN_DELETE(pPrimeClient); + return retVal; + } + + retVal = pPrimeClient->Connect(); + if (retVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_LINK_DEVICE, "Failed to connect to device: %s", xnGetStatusString(retVal)); + XN_ASSERT(FALSE); + XN_DELETE(pPrimeClient); + return retVal; + } + + if (performReset) + { + retVal = pPrimeClient->SoftReset(); + if (retVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_LINK_DEVICE, "Failed to reset device: %s", xnGetStatusString(retVal)); + XN_ASSERT(FALSE); + XN_DELETE(pPrimeClient); + return retVal; + } + } + + m_pSensor = pPrimeClient; + + XnInt32 value32; + if (XN_STATUS_OK == xnOSReadIntFromINI(m_configFile, CONFIG_DEVICE_SECTION, "UsbInterface", &value32)) + { + retVal = setProperty(PS_PROPERTY_USB_INTERFACE, &value32, sizeof(value32)); + if (retVal != XN_STATUS_OK) + { + XN_DELETE(pPrimeClient); + return retVal; + } + } + + if (!leanInit) + { + retVal = FillSupportedVideoModes(); + if (retVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_LINK_DEVICE, "Failed to read device video modes: %s", xnGetStatusString(retVal)); + XN_ASSERT(FALSE); + XN_DELETE(pPrimeClient); + return retVal; + } + } + + return XN_STATUS_OK; +} + +void LinkOniDevice::Destroy() +{ + if (m_pSensor == NULL) + { + return; + } + + // TODO can we actually create more than one? + //m_createdDevices.Remove(pPrimeClient->GetConnectionString()); + + m_pSensor->Disconnect(); + m_pSensor->Shutdown(); + XN_DELETE(m_pSensor); + m_pSensor = NULL; +} + +OniStatus LinkOniDevice::getSensorInfoList(OniSensorInfo** pSensors, int* numSensors) +{ + *numSensors = m_numSensors; + *pSensors = m_sensors; + + return ONI_STATUS_OK; +} + +oni::driver::StreamBase* LinkOniDevice::createStream(OniSensorType sensorType) +{ + LinkOniStream* pStream; + + if (sensorType == ONI_SENSOR_DEPTH) + { + pStream = XN_NEW(LinkOniDepthStream, m_configFile, m_pSensor, this); + } + else if (sensorType == ONI_SENSOR_IR) + { + pStream = XN_NEW(LinkOniIRStream, m_configFile, m_pSensor, this); + } + //else if (sensorType == ONI_SENSOR_COLOR) + //{ + // pStream = XN_NEW(LinkOniColorStream, &m_sensor, this); + //} + else + { + m_driverServices.errorLoggerAppend("LinkOniDevice: Can't create a stream of type %d", sensorType); + return NULL; + } + + XnStatus nRetVal = pStream->Init(); + if (nRetVal != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("LinkOniDevice: Can't initialize stream of type %d: %s", sensorType, xnGetStatusString(nRetVal)); + XN_DELETE(pStream); + return NULL; + } + + return pStream; +} + +void LinkOniDevice::destroyStream(oni::driver::StreamBase* pStream) +{ + XN_DELETE(pStream); +} + +OniStatus LinkOniDevice::getProperty(int propertyId, void* data, int* pDataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (propertyId) + { + case ONI_DEVICE_PROPERTY_FIRMWARE_VERSION: + { + XnDetailedVersion versions = m_pSensor->GetFWVersion(); + XnUInt32 nCharsWritten = 0; + XnStatus rc = xnOSStrFormat((XnChar*)data, *pDataSize, &nCharsWritten, "%d.%d.%d.%d-%s", versions.m_nMajor, versions.m_nMinor, versions.m_nMaintenance, versions.m_nBuild, versions.m_strModifier); + if (rc != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Couldn't get firmware version: %s\n", xnGetStatusString(rc)); + return ONI_STATUS_BAD_PARAMETER; + } + *pDataSize = nCharsWritten+1; + break; + } + + case ONI_DEVICE_PROPERTY_HARDWARE_VERSION: + ENSURE_PROP_SIZE_DO(*pDataSize, short) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d or %d or %d\n", *pDataSize, sizeof(short), sizeof(int), sizeof(uint64_t)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pSensor->GetHWVersion()); + break; + + case ONI_DEVICE_PROPERTY_SERIAL_NUMBER: + { + const XnChar *serialNumber = m_pSensor->GetSerialNumber(); + + if (xnOSStrCopy((XnChar*)data, serialNumber, *pDataSize) != XN_STATUS_OK) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, xnOSStrLen(serialNumber)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + break; + } + case ONI_DEVICE_PROPERTY_DRIVER_VERSION: + { + EXACT_PROP_SIZE_DO(*pDataSize, OniVersion) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniVersion)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + OniVersion* version = (OniVersion*)data; + version->major = XN_PS_MAJOR_VERSION; + version->minor = XN_PS_MINOR_VERSION; + version->maintenance = XN_PS_MAINTENANCE_VERSION; + version->build = XN_PS_BUILD_VERSION; + break; + } +/* case ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION: + { + m_pSensor + if (*pDataSize == sizeof(OniImageRegistrationMode)) + { + OniImageRegistrationMode* mode = (OniImageRegistrationMode*)data; + + // Find the depth stream in the sensor. + XnDeviceStream* pDepth = NULL; + XnStatus xnrc = m_sensor.GetStream(XN_STREAM_NAME_DEPTH, &pDepth); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Set the mode in the depth stream. + XnUInt64 val; + xnrc = pDepth->GetProperty(XN_STREAM_PROPERTY_REGISTRATION, &val); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + // Update the return value. + *mode = (val == 1) ? ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR : ONI_IMAGE_REGISTRATION_OFF; + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniImageRegistrationMode)); + return ONI_STATUS_ERROR; + } + } + break;*/ + + // Internal Link Properties + case LINK_PROP_FW_VERSION: + EXACT_PROP_SIZE_DO(*pDataSize, XnDetailedVersion) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, sizeof(XnDetailedVersion)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + *(XnDetailedVersion*)data = m_pSensor->GetFWVersion(); + break; + + case LINK_PROP_VERSIONS_INFO_COUNT: + { + ENSURE_PROP_SIZE(*pDataSize, int); + xnl::Array versions; + nRetVal = m_pSensor->GetComponentsVersions(versions); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + + ASSIGN_PROP_VALUE_INT(data, *pDataSize, versions.GetSize()); + break; + } + + //case : + //TODO: Implement Get emitter active + //break; + + case LINK_PROP_VERSIONS_INFO: + { + xnl::Array components; + nRetVal = m_pSensor->GetComponentsVersions(components); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + + XnUInt32 nExpectedSize = components.GetSize() * sizeof(XnComponentVersion); + if (*pDataSize != (int)nExpectedSize) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, nExpectedSize); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xnOSMemCopy(data, components.GetData(), nExpectedSize); + break; + } + + case PS_PROPERTY_USB_INTERFACE: + { + ENSURE_PROP_SIZE(*pDataSize, XnUInt8); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, 0); + XnUInt8 nInterface = 0; + nRetVal = m_pSensor->GetUsbAltInterface(nInterface); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + if (nInterface == 0) + { + *(XnUInt8*)data = PS_USB_INTERFACE_ISO_ENDPOINTS; + } + else if (nInterface == 1) + { + *(XnUInt8*)data = PS_USB_INTERFACE_BULK_ENDPOINTS; + } + else + { + XN_ASSERT(FALSE); + return ONI_STATUS_ERROR; + } + } + break; + + case LINK_PROP_BOOT_STATUS: + { + EXACT_PROP_SIZE(*pDataSize, XnBootStatus); + XnBootStatus* pStatus = (XnBootStatus*)data; + nRetVal = m_pSensor->GetBootStatus(*pStatus); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + } + break; + + default: + return ONI_STATUS_BAD_PARAMETER; + } + + return ONI_STATUS_OK; +} + +OniStatus LinkOniDevice::setProperty(int propertyId, const void* data, int dataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (propertyId) + { +/* case ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION: + { + if (dataSize == sizeof(OniImageRegistrationMode)) + { + OniImageRegistrationMode* mode = (OniImageRegistrationMode*)data; + + // Find the depth stream in the sensor. + XnDeviceStream* pDepth = NULL; + XnStatus xnrc = m_sensor.GetStream(XN_STREAM_NAME_DEPTH, &pDepth); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_BAD_PARAMETER; + } + + // Set the mode in the depth stream. + XnUInt64 val = (*mode == ONI_IMAGE_REGISTRATION_DEPTH_TO_COLOR) ? 1 : 0; + xnrc = pDepth->SetProperty(XN_STREAM_PROPERTY_REGISTRATION, val); + if (xnrc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(OniImageRegistrationMode)); + return ONI_STATUS_ERROR; + } + } + break;*/ + + + // Internal Link Properties + // int props + case LINK_PROP_EMITTER_ACTIVE: + nRetVal = m_pSensor->SetEmitterActive(*(XnBool*)data); + XN_IS_STATUS_OK_LOG_ERROR_RET("Set emitter active", nRetVal, ONI_STATUS_ERROR); + break; + + // string props + case LINK_PROP_PRESET_FILE: + nRetVal = m_pSensor->RunPresetFile((XnChar *)data); + XN_IS_STATUS_OK_LOG_ERROR_RET("RunPresetFile", nRetVal, ONI_STATUS_ERROR); + break; + + case PS_PROPERTY_USB_INTERFACE: + { + ENSURE_PROP_SIZE(dataSize, XnUInt8); + XnUsbInterfaceType type = (XnUsbInterfaceType)*(XnUInt8*)data; + if (type == PS_USB_INTERFACE_ISO_ENDPOINTS) + { + nRetVal = m_pSensor->SetUsbAltInterface(0); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + } + else if (type == PS_USB_INTERFACE_BULK_ENDPOINTS) + { + nRetVal = m_pSensor->SetUsbAltInterface(1); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + } + else if (type != PS_USB_INTERFACE_DONT_CARE) + { + return ONI_STATUS_BAD_PARAMETER; + } + } + break; + + default: + return ONI_STATUS_BAD_PARAMETER; + } + + return ONI_STATUS_OK; +} + +OniBool LinkOniDevice::isPropertySupported(int propertyId) +{ + switch (propertyId) + { + case ONI_DEVICE_PROPERTY_FIRMWARE_VERSION: + case ONI_DEVICE_PROPERTY_HARDWARE_VERSION: + case ONI_DEVICE_PROPERTY_SERIAL_NUMBER: + case ONI_DEVICE_PROPERTY_DRIVER_VERSION: + //case ONI_DEVICE_PROPERTY_IMAGE_REGISTRATION: + + // Internal Link Properties + case LINK_PROP_FW_VERSION: + case LINK_PROP_VERSIONS_INFO_COUNT: + case LINK_PROP_VERSIONS_INFO: + case LINK_PROP_EMITTER_ACTIVE: + case LINK_PROP_PRESET_FILE: + case PS_PROPERTY_USB_INTERFACE: + case LINK_PROP_BOOT_STATUS: + return true; + default: + return false; + } +} + +void LinkOniDevice::notifyAllProperties() +{ + XnDetailedVersion version; + int size = sizeof(version); + getProperty(LINK_PROP_FW_VERSION, &version, &size); + raisePropertyChanged(LINK_PROP_FW_VERSION, &version, size); + + XnUInt8 altusb; + size = sizeof(altusb); + getProperty(PS_PROPERTY_USB_INTERFACE, &altusb, &size); + raisePropertyChanged(PS_PROPERTY_USB_INTERFACE, &altusb, size); +} + +OniStatus LinkOniDevice::invoke(int commandId, void* data, int dataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + switch (commandId) + { + case PS_COMMAND_AHB_READ: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandAHB) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandAHB)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandAHB* pPropReadAHB = reinterpret_cast(data); + nRetVal = m_pSensor->ReadAHB(pPropReadAHB->address, + static_cast(pPropReadAHB->offsetInBits), + static_cast(pPropReadAHB->widthInBits), + pPropReadAHB->value); + XN_IS_STATUS_OK_LOG_ERROR_RET("Read AHB", nRetVal, ONI_STATUS_ERROR); + break; + } + + case PS_COMMAND_AHB_WRITE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandAHB) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandAHB)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandAHB* pPropWriteAHB = reinterpret_cast(data); + nRetVal = m_pSensor->WriteAHB(pPropWriteAHB->address, + pPropWriteAHB->value, + static_cast(pPropWriteAHB->offsetInBits), + static_cast(pPropWriteAHB->widthInBits)); + XN_IS_STATUS_OK_LOG_ERROR_RET("Write AHB", nRetVal, ONI_STATUS_ERROR); + break; + } + + case PS_COMMAND_I2C_READ: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandI2C) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandI2C)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandI2C* pPropReadI2C = reinterpret_cast(data); + nRetVal = m_pSensor->ReadI2C( + static_cast(pPropReadI2C->deviceID), + static_cast(pPropReadI2C->addressSize), + pPropReadI2C->address, + static_cast(pPropReadI2C->valueSize), + pPropReadI2C->value); + XN_IS_STATUS_OK_LOG_ERROR_RET("Read I2C", nRetVal, ONI_STATUS_ERROR); + break; + } + + case PS_COMMAND_I2C_WRITE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandI2C) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandI2C)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandI2C* pPropWriteI2C = reinterpret_cast(data); + nRetVal = m_pSensor->WriteI2C(static_cast(pPropWriteI2C->deviceID), + static_cast(pPropWriteI2C->addressSize), + pPropWriteI2C->address, + static_cast(pPropWriteI2C->valueSize), + pPropWriteI2C->value, + pPropWriteI2C->mask); + XN_IS_STATUS_OK_LOG_ERROR_RET("Write I2C", nRetVal, ONI_STATUS_ERROR); + break; + } + + case PS_COMMAND_SOFT_RESET: + nRetVal = m_pSensor->SoftReset(); + XN_IS_STATUS_OK_LOG_ERROR_RET("Soft reset", nRetVal, ONI_STATUS_ERROR); + break; + + case PS_COMMAND_POWER_RESET: + nRetVal = m_pSensor->HardReset(); + XN_IS_STATUS_OK_LOG_ERROR_RET("Power reset", nRetVal, ONI_STATUS_ERROR); + break; + + case PS_COMMAND_BEGIN_FIRMWARE_UPDATE: + nRetVal = m_pSensor->BeginUploadFileOnControlEP(); + XN_IS_STATUS_OK_LOG_ERROR_RET("Begin update", nRetVal, ONI_STATUS_ERROR); + break; + + case PS_COMMAND_END_FIRMWARE_UPDATE: + nRetVal = m_pSensor->EndUploadFileOnControlEP(); + XN_IS_STATUS_OK_LOG_ERROR_RET("End update", nRetVal, ONI_STATUS_ERROR); + break; + + case PS_COMMAND_UPLOAD_FILE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandUploadFile) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandUploadFile)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandUploadFile* pArgs = reinterpret_cast(data); + nRetVal = m_pSensor->UploadFileOnControlEP(pArgs->filePath, pArgs->uploadToFactory); + XN_IS_STATUS_OK_LOG_ERROR_RET("Upload File", nRetVal, ONI_STATUS_ERROR); + } + break; + + case PS_COMMAND_DOWNLOAD_FILE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandDownloadFile) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandDownloadFile)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandDownloadFile* pArgs = reinterpret_cast(data); + nRetVal = m_pSensor->DownloadFile(static_cast(pArgs->zone), + pArgs->firmwareFileName, pArgs->targetPath); + XN_IS_STATUS_OK_LOG_ERROR_RET("Download File", nRetVal, ONI_STATUS_ERROR); + } + break; + + case PS_COMMAND_GET_FILE_LIST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandGetFileList) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandGetFileList)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandGetFileList* pArgs = reinterpret_cast(data); + if (pArgs->files == NULL) + { + m_driverServices.errorLoggerAppend("Files array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xnl::Array files; + nRetVal = m_pSensor->GetFileList(files); + XN_IS_STATUS_OK_LOG_ERROR_RET("Get file list", nRetVal, ONI_STATUS_ERROR); + + if (pArgs->count < files.GetSize()) + { + m_driverServices.errorLoggerAppend("Insufficient memory for files list. available: %d, required: %d\n", pArgs->count, files.GetSize()); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xnOSMemCopy(pArgs->files, files.GetData(), files.GetSize() * sizeof(XnFwFileEntry)); + pArgs->count = files.GetSize(); + } + break; + + case PS_COMMAND_FORMAT_ZONE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandFormatZone) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandFormatZone)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + const XnCommandFormatZone* pArgs = reinterpret_cast(data); + nRetVal = m_pSensor->FormatZone(pArgs->zone); + XN_IS_STATUS_OK_LOG_ERROR_RET("Format Zone", nRetVal, ONI_STATUS_ERROR); + } + break; + + case PS_COMMAND_DUMP_ENDPOINT: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandDumpEndpoint) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandDumpEndpoint)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + const XnCommandDumpEndpoint* pArgs = reinterpret_cast(data); + XnChar strDumpName[XN_FILE_MAX_PATH] = ""; + xnLinkGetEPDumpName(pArgs->endpoint, strDumpName, sizeof(strDumpName)); + xnDumpSetMaskState(strDumpName, pArgs->enabled); + } + break; + + case PS_COMMAND_GET_I2C_DEVICE_LIST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandGetI2CDeviceList) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandGetI2CDeviceList)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandGetI2CDeviceList* pArgs = reinterpret_cast(data); + if (pArgs->devices == NULL) + { + m_driverServices.errorLoggerAppend("Devices array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xnl::Array devices; + nRetVal = m_pSensor->GetSupportedI2CDevices(devices); + XN_IS_STATUS_OK_LOG_ERROR_RET("Get i2c device list", nRetVal, ONI_STATUS_ERROR); + + if (pArgs->count < devices.GetSize()) + { + m_driverServices.errorLoggerAppend("Insufficient memory for device list. available: %d, required: %d\n", pArgs->count, devices.GetSize()); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + for (int i = 0; i < (int)devices.GetSize(); ++i) + { + pArgs->devices[i].id = devices[i].m_nID; + xnOSStrCopy(pArgs->devices[i].name, devices[i].m_strName, sizeof(pArgs->devices[i].name)); + } + pArgs->count = devices.GetSize(); + } + break; + + case PS_COMMAND_GET_BIST_LIST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandGetBistList) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandGetBistList)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandGetBistList* pArgs = reinterpret_cast(data); + if (pArgs->tests == NULL) + { + m_driverServices.errorLoggerAppend("Bist array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xnl::Array tests; + nRetVal = m_pSensor->GetSupportedBistTests(tests); + XN_IS_STATUS_OK_LOG_ERROR_RET("Get bist list", nRetVal, ONI_STATUS_ERROR); + + if (pArgs->count < tests.GetSize()) + { + m_driverServices.errorLoggerAppend("Insufficient memory for tests list. available: %d, required: %d\n", pArgs->count, tests.GetSize()); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + for (int i = 0; i < (int)tests.GetSize(); ++i) + { + pArgs->tests[i] = tests[i]; + } + pArgs->count = tests.GetSize(); + } + break; + + case PS_COMMAND_EXECUTE_BIST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandExecuteBist) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandExecuteBist)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandExecuteBist* pArgs = reinterpret_cast(data); + if (pArgs->extraData == NULL) + { + m_driverServices.errorLoggerAppend("extra data array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + nRetVal = m_pSensor->ExecuteBist(pArgs->id, pArgs->errorCode, pArgs->extraDataSize, pArgs->extraData); + XN_IS_STATUS_OK_LOG_ERROR_RET("execute bist", nRetVal, ONI_STATUS_ERROR); + } + break; + + case PS_COMMAND_USB_TEST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandUsbTest) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandUsbTest)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandUsbTest* pArgs = reinterpret_cast(data); + if (pArgs->endpoints == NULL) + { + m_driverServices.errorLoggerAppend("Endpoints array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + nRetVal = m_pSensor->UsbTest(pArgs->seconds, pArgs->endpointCount, pArgs->endpoints); + XN_IS_STATUS_OK_LOG_ERROR_RET("Usb test", nRetVal, ONI_STATUS_ERROR); + } + break; + + case PS_COMMAND_GET_LOG_MASK_LIST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandGetLogMaskList) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandGetLogMaskList)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandGetLogMaskList* pArgs = reinterpret_cast(data); + if (pArgs->masks == NULL) + { + m_driverServices.errorLoggerAppend("Mask array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xnl::Array masks; + nRetVal = m_pSensor->GetSupportedLogFiles(masks); + XN_IS_STATUS_OK_LOG_ERROR_RET("Get log mask list", nRetVal, ONI_STATUS_ERROR); + + if (pArgs->count < masks.GetSize()) + { + m_driverServices.errorLoggerAppend("Insufficient memory for masks list. available: %d, required: %d\n", pArgs->count, masks.GetSize()); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + for (int i = 0; i < (int)masks.GetSize(); ++i) + { + pArgs->masks[i].id = masks[i].m_nID; + xnOSStrCopy(pArgs->masks[i].name, masks[i].m_strName, sizeof(pArgs->masks[i].name)); + } + pArgs->count = masks.GetSize(); + } + break; + + case PS_COMMAND_SET_LOG_MASK_STATE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandSetLogMaskState) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandSetLogMaskState)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandSetLogMaskState* pArgs = reinterpret_cast(data); + if (pArgs->enabled) + { + nRetVal = m_pSensor->OpenFWLogFile((XnUInt8)pArgs->mask); + } + else + { + nRetVal = m_pSensor->CloseFWLogFile((XnUInt8)pArgs->mask); + } + XN_IS_STATUS_OK_LOG_ERROR_RET("Set log mask state", nRetVal, ONI_STATUS_ERROR); + } + break; + + case PS_COMMAND_START_LOG: + { + nRetVal = m_pSensor->StartFWLog(); + XN_IS_STATUS_OK_LOG_ERROR_RET("Start log", nRetVal, ONI_STATUS_ERROR); + } + break; + + case PS_COMMAND_STOP_LOG: + { + nRetVal = m_pSensor->StopFWLog(); + XN_IS_STATUS_OK_LOG_ERROR_RET("Stop log", nRetVal, ONI_STATUS_ERROR); + } + break; + + case LINK_COMMAND_GET_FW_STREAM_LIST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandGetFwStreamList) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandGetLogMaskList)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandGetFwStreamList* pArgs = reinterpret_cast(data); + if (pArgs->streams == NULL) + { + m_driverServices.errorLoggerAppend("Streams array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xnl::Array streams; + nRetVal = m_pSensor->EnumerateStreams(streams); + XN_IS_STATUS_OK_LOG_ERROR_RET("Get log mask list", nRetVal, ONI_STATUS_ERROR); + + if (pArgs->count < streams.GetSize()) + { + m_driverServices.errorLoggerAppend("Insufficient memory for stream list. available: %d, required: %d\n", pArgs->count, streams.GetSize()); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + for (int i = 0; i < (int)streams.GetSize(); ++i) + { + pArgs->streams[i] = streams[i]; + } + pArgs->count = streams.GetSize(); + } + break; + + case LINK_COMMAND_CREATE_FW_STREAM: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandCreateStream) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandCreateStream)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandCreateStream* pArgs = reinterpret_cast(data); + XnUInt16 id; + nRetVal = m_pSensor->CreateInputStream(pArgs->type, pArgs->creationInfo, id); + XN_IS_STATUS_OK_LOG_ERROR_RET("Create stream", nRetVal, ONI_STATUS_ERROR); + pArgs->id = id; + } + break; + + case LINK_COMMAND_DESTROY_FW_STREAM: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandDestroyStream) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandDestroyStream)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandDestroyStream* pArgs = reinterpret_cast(data); + nRetVal = m_pSensor->DestroyInputStream((XnUInt16)pArgs->id); + XN_IS_STATUS_OK_LOG_ERROR_RET("Destroy stream", nRetVal, ONI_STATUS_ERROR); + } + break; + + case LINK_COMMAND_START_FW_STREAM: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandStartStream) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandStartStream)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandStartStream* pArgs = reinterpret_cast(data); + xn::LinkInputStream* pInputStream = m_pSensor->GetInputStream((XnUInt16)pArgs->id); + if (pInputStream == NULL) + { + m_driverServices.errorLoggerAppend("Stream with ID %d wasn't created\n", pArgs->id); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + nRetVal = pInputStream->Start(); + XN_IS_STATUS_OK_LOG_ERROR_RET("Start stream", nRetVal, ONI_STATUS_ERROR); + } + break; + + case LINK_COMMAND_STOP_FW_STREAM: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandStopStream) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandStopStream)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandStopStream* pArgs = reinterpret_cast(data); + xn::LinkInputStream* pInputStream = m_pSensor->GetInputStream((XnUInt16)pArgs->id); + if (pInputStream == NULL) + { + m_driverServices.errorLoggerAppend("Stream with ID %d wasn't created\n", pArgs->id); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + nRetVal = pInputStream->Stop(); + XN_IS_STATUS_OK_LOG_ERROR_RET("Stop stream", nRetVal, ONI_STATUS_ERROR); + } + break; + + case LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE_LIST: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandGetFwStreamVideoModeList) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandGetFwStreamVideoModeList)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandGetFwStreamVideoModeList* pArgs = reinterpret_cast(data); + if (pArgs->videoModes == NULL) + { + m_driverServices.errorLoggerAppend("Streams array must point to valid memory: \n"); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xn::LinkInputStream* pInputStream = m_pSensor->GetInputStream((XnUInt16)pArgs->streamId); + if (pInputStream == NULL) + { + m_driverServices.errorLoggerAppend("Stream with ID %d wasn't created\n", pArgs->streamId); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + if (pInputStream->GetStreamFragLevel() != XN_LINK_STREAM_FRAG_LEVEL_FRAMES) + { + m_driverServices.errorLoggerAppend("Stream with ID %d is not a frame stream\n", pArgs->streamId); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xn::LinkFrameInputStream* pFrameInputStream = (xn::LinkFrameInputStream*)pInputStream; + xnl::Array videoModes = pFrameInputStream->GetSupportedVideoModes(); + if (pArgs->count < videoModes.GetSize()) + { + m_driverServices.errorLoggerAppend("Insufficient memory for stream list. available: %d, required: %d\n", pArgs->count, videoModes.GetSize()); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + for (int i = 0; i < (int)videoModes.GetSize(); ++i) + { + pArgs->videoModes[i] = videoModes[i]; + } + pArgs->count = videoModes.GetSize(); + } + break; + + case LINK_COMMAND_SET_FW_STREAM_VIDEO_MODE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandSetFwStreamVideoMode) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandSetFwStreamVideoMode)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandSetFwStreamVideoMode* pArgs = reinterpret_cast(data); + + xn::LinkInputStream* pInputStream = m_pSensor->GetInputStream((XnUInt16)pArgs->streamId); + if (pInputStream == NULL) + { + m_driverServices.errorLoggerAppend("Stream with ID %d wasn't created\n", pArgs->streamId); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + if (pInputStream->GetStreamFragLevel() != XN_LINK_STREAM_FRAG_LEVEL_FRAMES) + { + m_driverServices.errorLoggerAppend("Stream with ID %d is not a frame stream\n", pArgs->streamId); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xn::LinkFrameInputStream* pFrameInputStream = (xn::LinkFrameInputStream*)pInputStream; + nRetVal = pFrameInputStream->SetVideoMode(pArgs->videoMode); + XN_IS_STATUS_OK_LOG_ERROR_RET("Set video mode", nRetVal, ONI_STATUS_ERROR); + } + break; + + case LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE: + { + EXACT_PROP_SIZE_DO(dataSize, XnCommandGetFwStreamVideoMode) + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", dataSize, sizeof(XnCommandGetFwStreamVideoMode)); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + XnCommandGetFwStreamVideoMode* pArgs = reinterpret_cast(data); + + xn::LinkInputStream* pInputStream = m_pSensor->GetInputStream((XnUInt16)pArgs->streamId); + if (pInputStream == NULL) + { + m_driverServices.errorLoggerAppend("Stream with ID %d wasn't created\n", pArgs->streamId); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + if (pInputStream->GetStreamFragLevel() != XN_LINK_STREAM_FRAG_LEVEL_FRAMES) + { + m_driverServices.errorLoggerAppend("Stream with ID %d is not a frame stream\n", pArgs->streamId); + XN_ASSERT(FALSE); + return ONI_STATUS_BAD_PARAMETER; + } + + xn::LinkFrameInputStream* pFrameInputStream = (xn::LinkFrameInputStream*)pInputStream; + pArgs->videoMode = pFrameInputStream->GetVideoMode(); + } + break; + + default: + return DeviceBase::invoke(commandId, data, dataSize); + } + + return ONI_STATUS_OK; +} + +OniBool LinkOniDevice::isCommandSupported(int commandId) +{ + switch (commandId) + { + case PS_COMMAND_AHB_READ: + case PS_COMMAND_AHB_WRITE: + case PS_COMMAND_I2C_READ: + case PS_COMMAND_I2C_WRITE: + case PS_COMMAND_SOFT_RESET: + case PS_COMMAND_POWER_RESET: + case PS_COMMAND_BEGIN_FIRMWARE_UPDATE: + case PS_COMMAND_END_FIRMWARE_UPDATE: + case PS_COMMAND_UPLOAD_FILE: + case PS_COMMAND_DOWNLOAD_FILE: + case PS_COMMAND_GET_FILE_LIST: + case PS_COMMAND_FORMAT_ZONE: + case PS_COMMAND_DUMP_ENDPOINT: + case PS_COMMAND_GET_I2C_DEVICE_LIST: + case PS_COMMAND_GET_BIST_LIST: + case PS_COMMAND_EXECUTE_BIST: + case PS_COMMAND_USB_TEST: + case PS_COMMAND_GET_LOG_MASK_LIST: + case PS_COMMAND_SET_LOG_MASK_STATE: + case PS_COMMAND_START_LOG: + case PS_COMMAND_STOP_LOG: + case LINK_COMMAND_GET_FW_STREAM_LIST: + case LINK_COMMAND_CREATE_FW_STREAM: + case LINK_COMMAND_DESTROY_FW_STREAM: + case LINK_COMMAND_START_FW_STREAM: + case LINK_COMMAND_STOP_FW_STREAM: + case LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE_LIST: + case LINK_COMMAND_SET_FW_STREAM_VIDEO_MODE: + case LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE: + return true; + default: + return DeviceBase::isCommandSupported(commandId); + } +} diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniDevice.h b/Source/Drivers/PSLink/DriverImpl/LinkOniDevice.h new file mode 100644 index 0000000..0abe521 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniDevice.h @@ -0,0 +1,102 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __LINK_ONI_DEVICE_H__ +#define __LINK_ONI_DEVICE_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include "LinkOniDepthStream.h" +#include "LinkOniIRStream.h" +//#include "LinkOniColorStream.h" + +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- + +class LinkOniStream; +class LinkOniDriver; + +class LinkOniDevice : + public oni::driver::DeviceBase +{ +public: + LinkOniDevice(const char* configFile, const char* uri, oni::driver::DriverServices& driverServices, LinkOniDriver* pDriver); + virtual ~LinkOniDevice(); + + XnStatus Init(const char* mode); + void Destroy(); + + OniDeviceInfo* GetInfo() { return &m_info; } + + OniStatus getSensorInfoList(OniSensorInfo** pSensors, int* numSensors); + + oni::driver::StreamBase* createStream(OniSensorType sensorType); + void destroyStream(oni::driver::StreamBase* pStream); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + virtual OniBool isPropertySupported(int propertyId); + virtual void notifyAllProperties(); + + virtual OniStatus invoke(int commandId, void* data, int dataSize); + virtual OniBool isCommandSupported(int commandId); + + //virtual OniStatus EnableFrameSync(LinkOniStream** pStreams, int streamCount); + //virtual void DisableFrameSync(); + + //virtual OniBool isImageRegistrationModeSupported(OniImageRegistrationMode mode); + + xn::PrimeClient* GetSensor() + { + return m_pSensor; + } + + LinkOniDriver* GetDriver() + { + return m_pDriver; + } + +private: + XN_DISABLE_COPY_AND_ASSIGN(LinkOniDevice) + + XnStatus readSupportedModesFromStream(XnFwStreamInfo &info, xnl::Array &aSupportedModes); + XnStatus FillSupportedVideoModes(); + + const char* m_configFile; + OniDeviceInfo m_info; + // the device we wrap + xn::PS1200Device* m_pSensor; + + // what we supply + int m_numSensors; + OniSensorInfo m_sensors[10]; + + oni::driver::DriverServices& m_driverServices; + + LinkOniDriver* m_pDriver; +}; + +#endif // __LINK_ONI_DEVICE_H__ diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniDriver.cpp b/Source/Drivers/PSLink/DriverImpl/LinkOniDriver.cpp new file mode 100644 index 0000000..eee80b1 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniDriver.cpp @@ -0,0 +1,304 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniDriver.h" +#include "LinkOniDevice.h" +#include "LinkDeviceEnumeration.h" +#include +#include + +//--------------------------------------------------------------------------- +// LinkOniDriver class +//--------------------------------------------------------------------------- +LinkOniDriver::LinkOpenNILogWriter::LinkOpenNILogWriter(OniDriverServices* pDriverServices) : m_pDriverServices(pDriverServices) +{ +} + +void LinkOniDriver::LinkOpenNILogWriter::WriteEntry(const XnLogEntry* pEntry) +{ + m_pDriverServices->log(m_pDriverServices, pEntry->nSeverity, pEntry->strFile, pEntry->nLine, pEntry->strMask, pEntry->strMessage); +} + +void LinkOniDriver::LinkOpenNILogWriter::WriteUnformatted(const XnChar* /*strMessage*/) +{ + // DO NOTHING +} + +OniStatus LinkOniDriver::initialize(oni::driver::DeviceConnectedCallback deviceConnectedCallback, oni::driver::DeviceDisconnectedCallback deviceDisconnectedCallback, oni::driver::DeviceStateChangedCallback deviceStateChangedCallback, void* pCookie) +{ + OniStatus nRetVal = DriverBase::initialize(deviceConnectedCallback, deviceDisconnectedCallback, deviceStateChangedCallback, pCookie); + if (nRetVal != ONI_STATUS_OK) + { + return (nRetVal); + } + + xnLogSetMaskMinSeverity(XN_LOG_MASK_ALL, XN_LOG_VERBOSE); + m_writer.Register(); + + XnStatus rc = LinkDeviceEnumeration::ConnectedEvent().Register(OnDeviceConnected, this, m_connectedEventHandle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + rc = LinkDeviceEnumeration::DisconnectedEvent().Register(OnDeviceDisconnected, this, m_disconnectedEventHandle); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + rc = LinkDeviceEnumeration::Initialize(); + if (rc != XN_STATUS_OK) + { + return ONI_STATUS_ERROR; + } + + resolveConfigFilePath(); + + return ONI_STATUS_OK; +} + +void LinkOniDriver::shutdown() +{ + if (m_connectedEventHandle != NULL) + { + LinkDeviceEnumeration::ConnectedEvent().Unregister(m_connectedEventHandle); + m_connectedEventHandle = NULL; + } + + if (m_disconnectedEventHandle != NULL) + { + LinkDeviceEnumeration::DisconnectedEvent().Unregister(m_disconnectedEventHandle); + m_disconnectedEventHandle = NULL; + } + + // Close all open devices and release the memory + for (xnl::StringsHash::Iterator it = m_devices.Begin(); it != m_devices.End(); ++it) + { + XN_DELETE(it->Value()); + } + + m_devices.Clear(); + + LinkDeviceEnumeration::Shutdown(); +} + +oni::driver::DeviceBase* LinkOniDriver::deviceOpen(const char* uri, const char* mode) +{ + LinkOniDevice* pDevice = NULL; + + // if device was already opened for this uri, return the previous one + if (m_devices.Get(uri, pDevice) == XN_STATUS_OK) + { + getServices().errorLoggerAppend("Device is already open."); + return NULL; + } + + pDevice = XN_NEW(LinkOniDevice, m_configFilePath, uri, getServices(), this); + XnStatus nRetVal = pDevice->Init(mode); + if (nRetVal != XN_STATUS_OK) + { + getServices().errorLoggerAppend("Could not open \"%s\": %s", uri, xnGetStatusString(nRetVal)); + return NULL; + } + +/* TODO impl + // Register to error state property changed. + XnCallbackHandle handle; + nRetVal = pDevice->GetSensor()->RegisterToPropertyChange(XN_MODULE_NAME_DEVICE, + XN_MODULE_PROPERTY_ERROR_STATE, + OnDevicePropertyChanged, pDevice, handle); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pDevice); + return NULL; + } +*/ + + // Add the device and return it. + m_devices[uri] = pDevice; + return pDevice; +} + +void LinkOniDriver::deviceClose(oni::driver::DeviceBase* pDevice) +{ + for (xnl::StringsHash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (iter->Value() == pDevice) + { + m_devices.Remove(iter); + XN_DELETE(pDevice); + return; + } + } + + // not our device?! + XN_ASSERT(FALSE); +} + +/* +void* LinkOniDriver::enableFrameSync(oni::driver::StreamBase** pStreams, int streamCount) +{ + // Make sure all the streams belong to same device. + LinkOniDevice* pDevice = NULL; + for (int i = 0; i < streamCount; ++i) + { + LinkOniStream* pStream = dynamic_cast(pStreams[i]); + if (pStreams == NULL) + { + // Not allowed. + return NULL; + } + + // Check if device was not set before. + if (pDevice == NULL) + { + pDevice = pStream->GetDevice(); + } + // Compare device to stream's device. + else if (pDevice != pStream->GetDevice()) + { + // Streams from different devices are currently not allowed. + return NULL; + } + } + + // Create the frame sync group handle. + FrameSyncGroup* pFrameSyncGroup = XN_NEW(FrameSyncGroup); + if (pFrameSyncGroup == NULL) + { + return NULL; + } + pFrameSyncGroup->pDevice = pDevice; + + // Enable the frame sync. + OniStatus rc = pDevice->EnableFrameSync((LinkOniStream**)pStreams, streamCount); + if (rc != ONI_STATUS_OK) + { + XN_DELETE(pFrameSyncGroup); + return NULL; + } + + // Return the created handle. + return pFrameSyncGroup; +} + +void LinkOniDriver::disableFrameSync(void* frameSyncGroup) +{ + FrameSyncGroup* pFrameSyncGroup = (FrameSyncGroup*)frameSyncGroup; + + // Find device in driver. + xnl::StringsHash::ConstIterator iter = m_devices.Begin(); + while (iter != m_devices.End()) + { + // Make sure device belongs to driver. + if ((*iter).second == pFrameSyncGroup->pDevice) + { + // Disable frame sync in device. + pFrameSyncGroup->pDevice->DisableFrameSync(); + return; + } + ++iter; + } +} +*/ + +void XN_CALLBACK_TYPE LinkOniDriver::OnDevicePropertyChanged(const XnChar* /*ModuleName*/, XnUInt32 /*nPropertyId*/, void* /*pCookie*/) +{ + //TODO impl! + /* + LinkOniDevice* pDevice = (LinkOniDevice*)pCookie; + LinkOniDriver* pThis = pDevice->GetDriver(); + + if (nPropertyId == XN_MODULE_PROPERTY_ERROR_STATE) + { + XnSensor* pSensor = (XnSensor*)pDevice->GetSensor(); + + // Get the property value. + XnUInt64 errorState = 0; + XnStatus nRetVal = pSensor->GetProperty(ModuleName, XN_MODULE_PROPERTY_ERROR_STATE, &errorState); + if (nRetVal == XN_STATUS_OK) + { + if (errorState == XN_STATUS_DEVICE_NOT_CONNECTED) + { + pThis->deviceDisconnected(pDevice->GetInfo()); + } + else + { + int errorStateValue = XN_ERROR_STATE_OK; + switch (errorState) + { + case XN_STATUS_OK: + { + errorStateValue = XN_ERROR_STATE_OK; + break; + } + case XN_STATUS_DEVICE_PROJECTOR_FAULT: + { + errorStateValue = XN_ERROR_STATE_DEVICE_PROJECTOR_FAULT; + break; + } + case XN_STATUS_DEVICE_OVERHEAT: + { + errorStateValue = XN_ERROR_STATE_DEVICE_OVERHEAT; + break; + } + default: + { + // Invalid value. + XN_ASSERT(FALSE); + } + } + pThis->deviceStateChanged(pDevice->GetInfo(), errorStateValue); + } + } + } + */ +} + +void XN_CALLBACK_TYPE LinkOniDriver::OnDeviceConnected(const OniDeviceInfo& deviceInfo, void* pCookie) +{ + LinkOniDriver* pThis = (LinkOniDriver*)pCookie; + pThis->deviceConnected(&deviceInfo); +} + +void XN_CALLBACK_TYPE LinkOniDriver::OnDeviceDisconnected(const OniDeviceInfo& deviceInfo, void* pCookie) +{ + LinkOniDriver* pThis = (LinkOniDriver*)pCookie; + pThis->deviceDisconnected(&deviceInfo); +} + +void LinkOniDriver::resolveConfigFilePath() +{ + XnChar strModulePath[XN_FILE_MAX_PATH]; + + if (xnOSGetModulePathForProcAddress(reinterpret_cast(&LinkOniDriver::OnDeviceConnected), strModulePath) != XN_STATUS_OK || + xnOSGetDirName(strModulePath, m_configFilePath, sizeof(m_configFilePath)) != XN_STATUS_OK) + { + // Something wrong happened. Use the current directory as the fall-back. + xnOSStrCopy(m_configFilePath, ".", sizeof(m_configFilePath)); + } + + xnOSAppendFilePath(m_configFilePath, "PSLink.ini", sizeof(m_configFilePath)); +} + diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniDriver.h b/Source/Drivers/PSLink/DriverImpl/LinkOniDriver.h new file mode 100644 index 0000000..0e53e40 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniDriver.h @@ -0,0 +1,91 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __LINK_ONI_DRIVER_H__ +#define __LINK_ONI_DRIVER_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include "LinkOniDevice.h" +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class LinkOniDriver : + public oni::driver::DriverBase +{ +public: + LinkOniDriver(OniDriverServices* pDriverServices) : DriverBase(pDriverServices), m_writer(pDriverServices), m_connectedEventHandle(NULL), m_disconnectedEventHandle(NULL) + { + m_configFilePath[0] = '\0'; + } + + virtual OniStatus initialize(oni::driver::DeviceConnectedCallback deviceConnectedCallback, oni::driver::DeviceDisconnectedCallback deviceDisconnectedCallback, oni::driver::DeviceStateChangedCallback deviceStateChangedCallback, void* pCookie); + virtual void shutdown(); + + virtual oni::driver::DeviceBase* deviceOpen(const char* uri, const char* mode); + virtual void deviceClose(oni::driver::DeviceBase* pDevice); + + /* + virtual void* enableFrameSync(oni::driver::StreamBase** pStreams, int streamCount); + virtual void disableFrameSync(void* frameSyncGroup); + */ + + void ClearDevice(const char* uri); + +protected: + static void XN_CALLBACK_TYPE OnDevicePropertyChanged(const XnChar* ModuleName, XnUInt32 nPropertyId, void* pCookie); + static void XN_CALLBACK_TYPE OnDeviceConnected(const OniDeviceInfo& deviceInfo, void* pCookie); + static void XN_CALLBACK_TYPE OnDeviceDisconnected(const OniDeviceInfo& deviceInfo, void* pCookie); + + //uri -> LinkOniDevice map + xnl::StringsHash m_devices; + +private: + class LinkOpenNILogWriter : public XnLogWriterBase + { + public: + LinkOpenNILogWriter(OniDriverServices* pDriverServices); + virtual void WriteEntry(const XnLogEntry* pEntry); + virtual void WriteUnformatted(const XnChar* strMessage); + + private: + OniDriverServices* m_pDriverServices; + }; + + typedef struct + { + LinkOniDevice* pDevice; + } FrameSyncGroup; + + void resolveConfigFilePath(); + + LinkOpenNILogWriter m_writer; + XnCallbackHandle m_connectedEventHandle; + XnCallbackHandle m_disconnectedEventHandle; + char m_configFilePath[XN_FILE_MAX_PATH]; +}; + +#endif // __LINK_ONI_DRIVER_H__ diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.cpp b/Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.cpp new file mode 100644 index 0000000..f71270e --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.cpp @@ -0,0 +1,35 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniIRStream.h" + +//--------------------------------------------------------------------------- +// LinkOniIRStream class +//--------------------------------------------------------------------------- + +LinkOniIRStream::LinkOniIRStream(const char* configFile, xn::PrimeClient* pSensor, LinkOniDevice* pDevice) : + LinkOniMapStream(configFile, "IR", pSensor, ONI_SENSOR_IR, pDevice) +{ +} + + diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.h b/Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.h new file mode 100644 index 0000000..04b9993 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniIRStream.h @@ -0,0 +1,39 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __LINK_ONI_IR_STREAM_H__ +#define __LINK_ONI_IR_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniMapStream.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class LinkOniIRStream : + public LinkOniMapStream +{ +public: + LinkOniIRStream(const char* configFile, xn::PrimeClient* pSensor, LinkOniDevice* pDevice); +}; + +#endif // __LINK_ONI_IR_STREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.cpp b/Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.cpp new file mode 100644 index 0000000..f2698f6 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.cpp @@ -0,0 +1,331 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniMapStream.h" +//#include "LinkOniColorStream.h" +#include + +//--------------------------------------------------------------------------- +// LinkOniMapStream class +//--------------------------------------------------------------------------- + +LinkOniMapStream::LinkOniMapStream(const char* configFile, const char* configSection, xn::PrimeClient* pSensor, OniSensorType sensorType, LinkOniDevice* pDevice) : + LinkOniStream(configFile, configSection, pSensor, sensorType, pDevice), + m_nSupportedModesCount(0), + m_aSupportedModes(NULL) +{ +} + +LinkOniMapStream::~LinkOniMapStream() +{ + if (m_aSupportedModes != NULL) + { + XN_DELETE_ARR(m_aSupportedModes); + m_aSupportedModes = NULL; + } +} + +XnStatus LinkOniMapStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = LinkOniStream::Init(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = FillSupportedVideoModes(); + XN_IS_STATUS_OK(nRetVal); + + // read video mode + XnChar section[255]; + sprintf(section, "%s.VideoMode", m_configSection); + OniVideoMode videoMode; + GetVideoMode(&videoMode); + + XnInt32 temp32; + if (XN_STATUS_OK == xnOSReadIntFromINI(m_configFile, section, "XResolution", &temp32)) + { + videoMode.resolutionX = (int)temp32; + } + if (XN_STATUS_OK == xnOSReadIntFromINI(m_configFile, section, "YResolution", &temp32)) + { + videoMode.resolutionY = (int)temp32; + } + if (XN_STATUS_OK == xnOSReadIntFromINI(m_configFile, section, "FPS", &temp32)) + { + videoMode.fps = (int)temp32; + } + if (XN_STATUS_OK == xnOSReadIntFromINI(m_configFile, section, "PixelFormat", &temp32)) + { + videoMode.pixelFormat = (OniPixelFormat)temp32; + } + + nRetVal = SetVideoMode(&videoMode); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = setIntPropertyFromINI("LinkPixelFormat", LINK_PROP_PIXEL_FORMAT); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = setIntPropertyFromINI("Compression", LINK_PROP_COMPRESSION); + XN_IS_STATUS_OK(nRetVal); + + OniBool bMirror = TRUE; + if (XN_STATUS_OK == xnOSReadIntFromINI(m_configFile, section, "Mirror", &temp32)) + { + bMirror = (temp32 == 1); + } + + nRetVal = SetMirror(bMirror); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +OniStatus LinkOniMapStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + XnStatus nRetVal = XN_STATUS_ERROR; + + switch(propertyId) + { + case ONI_STREAM_PROPERTY_VIDEO_MODE: + EXACT_PROP_SIZE(*pDataSize, OniVideoMode); + nRetVal = GetVideoMode((OniVideoMode*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + + case ONI_STREAM_PROPERTY_MIRRORING: + EXACT_PROP_SIZE(*pDataSize, OniBool); + nRetVal = GetMirror((OniBool*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + + case ONI_STREAM_PROPERTY_CROPPING: + EXACT_PROP_SIZE(*pDataSize, OniCropping); + nRetVal = GetCropping(*(OniCropping*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + + case LINK_PROP_PIXEL_FORMAT: + ENSURE_PROP_SIZE(*pDataSize, XnLinkPixelFormat); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetVideoMode().m_nPixelFormat); + break; + + case LINK_PROP_COMPRESSION: + ENSURE_PROP_SIZE(*pDataSize, XnLinkCompressionType); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, m_pInputStream->GetVideoMode().m_nCompression); + break; + + default: + { + return LinkOniStream::getProperty(propertyId, data, pDataSize); + } + } + + return ONI_STATUS_OK; +} + +OniStatus LinkOniMapStream::setProperty(int propertyId, const void* data, int dataSize) +{ + XnStatus nRetVal = XN_STATUS_ERROR; + + switch(propertyId) + { + case ONI_STREAM_PROPERTY_VIDEO_MODE: + EXACT_PROP_SIZE(dataSize, OniVideoMode); + nRetVal = SetVideoMode((OniVideoMode*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + + case ONI_STREAM_PROPERTY_MIRRORING: + EXACT_PROP_SIZE(dataSize, OniBool); + nRetVal = SetMirror(*(OniBool*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + + case ONI_STREAM_PROPERTY_CROPPING: + EXACT_PROP_SIZE(dataSize, OniCropping); + nRetVal = SetCropping(*(OniCropping*)data); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + + case LINK_PROP_PIXEL_FORMAT: + { + ENSURE_PROP_SIZE(dataSize, XnLinkPixelFormat); + XnFwStreamVideoMode mode = m_pInputStream->GetVideoMode(); + mode.m_nPixelFormat = *(XnFwPixelFormat*)data; + nRetVal = m_pInputStream->SetVideoMode(mode); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + } + + case LINK_PROP_COMPRESSION: + { + ENSURE_PROP_SIZE(dataSize, XnLinkCompressionType); + XnFwStreamVideoMode mode = m_pInputStream->GetVideoMode(); + mode.m_nCompression = *(XnFwCompressionType*)data; + nRetVal = m_pInputStream->SetVideoMode(mode); + XN_IS_STATUS_OK_RET(nRetVal, ONI_STATUS_ERROR); + break; + } + + default: + return LinkOniStream::setProperty(propertyId, data, dataSize); + } + + return ONI_STATUS_OK; +} + +OniBool LinkOniMapStream::isPropertySupported(int propertyId) +{ + switch (propertyId) + { + case ONI_STREAM_PROPERTY_VIDEO_MODE: + case ONI_STREAM_PROPERTY_MIRRORING: + case ONI_STREAM_PROPERTY_CROPPING: + case LINK_PROP_PIXEL_FORMAT: + case LINK_PROP_COMPRESSION: + return true; + default: + return LinkOniStream::isPropertySupported(propertyId); + } +} + +void LinkOniMapStream::notifyAllProperties() +{ + LinkOniStream::notifyAllProperties(); + + int nValue; + int size = sizeof(int); + + getProperty(LINK_PROP_PIXEL_FORMAT, &nValue, &size); + raisePropertyChanged(LINK_PROP_PIXEL_FORMAT, &nValue, size); + + getProperty(LINK_PROP_COMPRESSION, &nValue, &size); + raisePropertyChanged(LINK_PROP_COMPRESSION, &nValue, size); +} + +XnStatus LinkOniMapStream::GetVideoMode(OniVideoMode* pVideoMode) +{ + // output format + pVideoMode->pixelFormat = m_pInputStream->GetOutputFormat(); + + // resolution + pVideoMode->resolutionX = (int)m_pInputStream->GetVideoMode().m_nXRes; + pVideoMode->resolutionY = (int)m_pInputStream->GetVideoMode().m_nYRes; + + // fps + pVideoMode->fps = (int)m_pInputStream->GetVideoMode().m_nFPS; + + return XN_STATUS_OK; +} + +XnStatus LinkOniMapStream::SetVideoMode(OniVideoMode* pVideoMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + OniVideoMode current; + GetVideoMode(¤t); + + if (!xnOSMemCmp(¤t, pVideoMode, sizeof(OniVideoMode))) + { + // nothing to do here + return (ONI_STATUS_OK); + } + + // now look for the first mode that matches + const xnl::Array& supportedModes = m_pInputStream->GetSupportedVideoModes(); + XnInt32 selectedIndex = -1; + for (XnUInt32 i = 0; i < supportedModes.GetSize(); ++i) + { + if (pVideoMode->resolutionX == (int)supportedModes[i].m_nXRes && + pVideoMode->resolutionY == (int)supportedModes[i].m_nYRes && + pVideoMode->fps == (int)supportedModes[i].m_nFPS) + { + selectedIndex = i; + break; + } + } + + if (selectedIndex == -1) + { + xnLogError(XN_MASK_LINK, "Tried to set unsupported mode: %ux%u@%u fps", + pVideoMode->resolutionX, pVideoMode->resolutionY, pVideoMode->fps); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + nRetVal = m_pInputStream->SetVideoMode(supportedModes[selectedIndex]); + XN_IS_STATUS_OK_LOG_ERROR("Set video mode", nRetVal); + + nRetVal = m_pInputStream->SetOutputFormat(pVideoMode->pixelFormat); + XN_IS_STATUS_OK_LOG_ERROR("Set output format", nRetVal); + + return XN_STATUS_OK; +} + +XnStatus LinkOniMapStream::FillSupportedVideoModes() +{ + int nCount; + const xnl::Array *pSupported; + pSupported = &m_pInputStream->GetSupportedVideoModes(); + nCount = (int)pSupported->GetSize(); + + m_aSupportedModes = XN_NEW_ARR(SupportedVideoMode, nCount); + XN_VALIDATE_ALLOC_PTR(m_aSupportedModes); + m_nSupportedModesCount = nCount; + + for (int i = 0; i < nCount; ++i) + { + m_aSupportedModes[i].nInputFormat = pSupported->GetData()[i].m_nPixelFormat; + + m_aSupportedModes[i].OutputMode.resolutionX = pSupported->GetData()[i].m_nXRes; + m_aSupportedModes[i].OutputMode.resolutionY = pSupported->GetData()[i].m_nYRes;; + m_aSupportedModes[i].OutputMode.fps = pSupported->GetData()[i].m_nFPS;; + m_aSupportedModes[i].OutputMode.pixelFormat = (OniPixelFormat)-1; // this field is not to be used here.; + } + + return (XN_STATUS_OK); +} + +XnStatus LinkOniMapStream::GetMirror(OniBool* pEnabled) +{ + *pEnabled = (OniBool)m_pInputStream->GetMirror(); + return (XN_STATUS_OK); +} + +XnStatus LinkOniMapStream::SetMirror(OniBool enabled) +{ + return m_pInputStream->SetMirror((XnBool)enabled); +} + +XnStatus LinkOniMapStream::GetCropping(OniCropping &cropping) +{ + const OniCropping &pCropping = m_pInputStream->GetCropping(); + xnOSMemCopy(&cropping, &pCropping, sizeof(OniCropping)); + return (XN_STATUS_OK); +} + +XnStatus LinkOniMapStream::SetCropping(const OniCropping &cropping) +{ + return m_pInputStream->SetCropping(cropping); +} + diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.h b/Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.h new file mode 100644 index 0000000..76d87e7 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniMapStream.h @@ -0,0 +1,69 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __LINK_ONI_MAP_STREAM_H__ +#define __LINK_ONI_MAP_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniStream.h" + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class LinkOniMapStream : + public LinkOniStream +{ +public: + LinkOniMapStream(const char* configFile, const char* configSection, xn::PrimeClient* pSensor, OniSensorType sensorType, LinkOniDevice* pDevice); + virtual ~LinkOniMapStream(); + + virtual XnStatus Init(); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + virtual OniBool isPropertySupported(int propertyId); + virtual void notifyAllProperties(); + + XnStatus GetVideoMode(OniVideoMode* pVideoMode); + XnStatus SetVideoMode(OniVideoMode* pVideoMode); + + XnStatus GetMirror(OniBool* pEnabled); + XnStatus SetMirror(OniBool enabled); + + XnStatus GetCropping(OniCropping &cropping); + XnStatus SetCropping(const OniCropping &cropping); + +protected: + struct SupportedVideoMode + { + OniVideoMode OutputMode; + XnFwPixelFormat nInputFormat; + }; + + int m_nSupportedModesCount; + SupportedVideoMode* m_aSupportedModes; + +private: + XnStatus FillSupportedVideoModes(); +}; + +#endif // __LINK_ONI_MAP_STREAM_H__ diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniStream.cpp b/Source/Drivers/PSLink/DriverImpl/LinkOniStream.cpp new file mode 100644 index 0000000..bf81bae --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniStream.cpp @@ -0,0 +1,186 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include "LinkOniStream.h" + +//--------------------------------------------------------------------------- +// LinkOniStream class +//--------------------------------------------------------------------------- +#define XN_MASK_LINK_STREAM "LinkStream" + +LinkOniStream::LinkOniStream(const char* configFile, const char* configSection, xn::PrimeClient* pSensor, OniSensorType sensorType, LinkOniDevice* pDevice) : + m_configFile(configFile), + m_configSection(configSection), + m_sensorType(sensorType), + m_pSensor(pSensor), + m_pDevice(pDevice), + m_pInputStream(NULL), + m_started(FALSE) +{ +} + +LinkOniStream::~LinkOniStream() +{ + destroy(); +} + +XnStatus LinkOniStream::Init() +{ + XnStatus nRetVal = XN_STATUS_OK; + XnLinkStreamType linkStreamType; + switch (m_sensorType) + { + case ONI_SENSOR_DEPTH: + linkStreamType = XN_LINK_STREAM_TYPE_SHIFTS; break; + //case ONI_SENSOR_COLOR: + // linkStreamType = XN_LINK_STREAM_TYPE_COLOR; break; + case ONI_SENSOR_IR: + linkStreamType = XN_LINK_STREAM_TYPE_IR; break; + default: + return XN_STATUS_BAD_PARAM; + } + nRetVal = m_pSensor->CreateInputStream(linkStreamType, NULL, m_streamId); + XN_IS_STATUS_OK(nRetVal); + + // TODO: make sure this cast doesn't make us problems + m_pInputStream = (xn::LinkFrameInputStream *)m_pSensor->GetInputStream(m_streamId); + XN_VALIDATE_OUTPUT_PTR(m_pInputStream); + + m_pInputStream->GetNewFrameEvent().Register(OnNewStreamDataEventHandler, this, m_hNewDataCallback); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = setIntPropertyFromINI("DumpData", PS_PROPERTY_DUMP_DATA); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +void LinkOniStream::setServices(oni::driver::StreamServices* pStreamServices) +{ + oni::driver::StreamBase::setServices(pStreamServices); + m_pInputStream->SetStreamServices(pStreamServices); +} + +void LinkOniStream::destroy() +{ + stop(); + m_pInputStream->GetNewFrameEvent().Unregister(m_hNewDataCallback); + m_pSensor->DestroyInputStream(m_streamId); +} + +OniStatus LinkOniStream::start() +{ + if (!m_started) + { + XnStatus nRetVal = m_pInputStream->Start(); + XN_IS_STATUS_OK_LOG_ERROR("Start streaming", (OniStatus)nRetVal); + + m_started = TRUE; + } + + return ONI_STATUS_OK; +} + +void LinkOniStream::stop() +{ + if (m_started) + { + m_started = FALSE; + + XnStatus nRetVal = m_pInputStream->Stop(); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK_STREAM, "Failed to stop streaming: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } + } +} + +OniStatus LinkOniStream::getProperty(int propertyId, void* data, int* pDataSize) +{ + switch(propertyId) + { + case PS_PROPERTY_DUMP_DATA: + { + XnChar strDumpName[XN_FILE_MAX_PATH] = ""; + xnLinkGetStreamDumpName(m_streamId, strDumpName, sizeof(strDumpName)); + XnBool bEnabled = xnLogIsDumpMaskEnabled(strDumpName); + ENSURE_PROP_SIZE(*pDataSize, bool); + ASSIGN_PROP_VALUE_INT(data, *pDataSize, bEnabled); + } + break; + default: + return ONI_STATUS_BAD_PARAMETER; + } + + return ONI_STATUS_OK; +} + +OniStatus LinkOniStream::setProperty(int propertyId, const void* data, int dataSize) +{ + switch(propertyId) + { + case PS_PROPERTY_DUMP_DATA: + { + int val; + GET_PROP_VALUE_INT(val, data, dataSize); + + XnChar strDumpName[XN_FILE_MAX_PATH] = ""; + xnLinkGetStreamDumpName(m_streamId, strDumpName, sizeof(strDumpName)); + xnDumpSetMaskState(strDumpName, val == 1); + } + break; + default: + return ONI_STATUS_BAD_PARAMETER; + } + + return ONI_STATUS_OK; +} + +OniBool LinkOniStream::isPropertySupported(int propertyId) +{ + return (propertyId == PS_PROPERTY_DUMP_DATA); +} + +void XN_CALLBACK_TYPE LinkOniStream::OnNewStreamDataEventHandler(const xn::NewFrameEventArgs& args, void* pCookie) +{ + LinkOniStream* pThis = (LinkOniStream*)pCookie; + if (pThis->m_started) + { + pThis->raiseNewFrame(args.pFrame); + } +} + +XnStatus LinkOniStream::setIntPropertyFromINI(const char* key, int propertyId) +{ + XnInt32 value; + if (XN_STATUS_OK == xnOSReadIntFromINI(m_configFile, m_configSection, key, &value)) + { + if (ONI_STATUS_OK != setProperty(propertyId, &value, sizeof(value))) + { + return XN_STATUS_ERROR; + } + } + + return XN_STATUS_OK; +} diff --git a/Source/Drivers/PSLink/DriverImpl/LinkOniStream.h b/Source/Drivers/PSLink/DriverImpl/LinkOniStream.h new file mode 100644 index 0000000..8da7968 --- /dev/null +++ b/Source/Drivers/PSLink/DriverImpl/LinkOniStream.h @@ -0,0 +1,84 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __LINK_ONI_STREAM_H__ +#define __LINK_ONI_STREAM_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include "PrimeClient.h" +#include + + +//using namespace oni::driver; +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +class XnDeviceStream; +class LinkOniDevice; + +class LinkOniStream : + public oni::driver::StreamBase +{ +public: + LinkOniStream(const char* configFile, const char* configSection, xn::PrimeClient* pSensor, OniSensorType sensorType, LinkOniDevice* pDevice); + ~LinkOniStream(); + + virtual XnStatus Init(); + + virtual void setServices(oni::driver::StreamServices* pStreamServices); + + virtual int getRequiredFrameSize() { return m_pInputStream->GetRequiredFrameSize(); } + + OniStatus start(); + void stop(); + + virtual OniStatus getProperty(int propertyId, void* data, int* pDataSize); + virtual OniStatus setProperty(int propertyId, const void* data, int dataSize); + virtual OniBool isPropertySupported(int propertyId); + + LinkOniDevice* GetDevice() { return m_pDevice; } + xn::LinkFrameInputStream* GetDeviceStream() { return m_pInputStream; } + +protected: + XnStatus setIntPropertyFromINI(const char* key, int propertyId); + + const char* m_configFile; + const char* m_configSection; + OniSensorType m_sensorType; + xn::PrimeClient* m_pSensor; + LinkOniDevice* m_pDevice; + + XnUInt16 m_streamId; + // TODO: should have used generic stream instead, but openni2 doesn't support non-frame streams yet. + xn::LinkFrameInputStream* m_pInputStream; + XnCallbackHandle m_hNewDataCallback; + +private: + void destroy(); + XnBool m_started; + + static void XN_CALLBACK_TYPE OnNewStreamDataEventHandler(const xn::NewFrameEventArgs& args, void* pCookie); +}; + +#endif // __LINK_ONI_STREAM_H__ diff --git a/Source/Drivers/PSLink/LinkDeviceEnumeration.cpp b/Source/Drivers/PSLink/LinkDeviceEnumeration.cpp new file mode 100644 index 0000000..d219bee --- /dev/null +++ b/Source/Drivers/PSLink/LinkDeviceEnumeration.cpp @@ -0,0 +1,174 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "LinkDeviceEnumeration.h" +#include "XnLinkProtoLibDefs.h" +#include + +//--------------------------------------------------------------------------- +// Globals +//--------------------------------------------------------------------------- +XnBool LinkDeviceEnumeration::ms_initialized = FALSE; +LinkDeviceEnumeration::DeviceConnectivityEvent LinkDeviceEnumeration::ms_connectedEvent; +LinkDeviceEnumeration::DeviceConnectivityEvent LinkDeviceEnumeration::ms_disconnectedEvent; +LinkDeviceEnumeration::DevicesHash LinkDeviceEnumeration::ms_devices; +xnl::Array LinkDeviceEnumeration::ms_aRegistrationHandles; +XN_CRITICAL_SECTION_HANDLE LinkDeviceEnumeration::ms_lock; + +LinkDeviceEnumeration::XnUsbId LinkDeviceEnumeration::ms_supportedProducts[] = +{ + { 0x1D27, 0x1250 }, + { 0x1D27, 0x1260 }, + { 0x1D27, 0x1270 }, + { 0x1D27, 0x1280 }, + { 0x1D27, 0x1290 }, + { 0x1D27, 0xf9db }, +}; + +XnUInt32 LinkDeviceEnumeration::ms_supportedProductsCount = sizeof(LinkDeviceEnumeration::ms_supportedProducts) / sizeof(LinkDeviceEnumeration::ms_supportedProducts[0]); + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus LinkDeviceEnumeration::Initialize() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (ms_initialized) + { + return XN_STATUS_OK; + } + + nRetVal = xnUSBInit(); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = xnOSCreateCriticalSection(&ms_lock); + XN_IS_STATUS_OK(nRetVal); + + const XnUSBConnectionString* astrDevicePaths; + XnUInt32 nCount; + + // check all products + for (XnUInt32 i = 0; i < ms_supportedProductsCount; ++i) + { + // register for USB events + XnRegistrationHandle hRegistration = NULL; + nRetVal = xnUSBRegisterToConnectivityEvents(ms_supportedProducts[i].vendorID, ms_supportedProducts[i].productID, OnConnectivityEventCallback, &ms_supportedProducts[i], &hRegistration); + XN_IS_STATUS_OK(nRetVal); + + nRetVal = ms_aRegistrationHandles.AddLast(hRegistration); + XN_IS_STATUS_OK(nRetVal); + + // and enumerate for existing ones + nRetVal = xnUSBEnumerateDevices(ms_supportedProducts[i].vendorID, ms_supportedProducts[i].productID, &astrDevicePaths, &nCount); + XN_IS_STATUS_OK(nRetVal); + + for (XnUInt32 j = 0; j < nCount; ++j) + { + OnConnectivityEvent(astrDevicePaths[j], XN_USB_EVENT_DEVICE_CONNECT, ms_supportedProducts[i]); + } + + xnUSBFreeDevicesList(astrDevicePaths); + } + + ms_initialized = TRUE; + + return XN_STATUS_OK; +} + +void LinkDeviceEnumeration::Shutdown() +{ + if (ms_initialized) + { + for (XnUInt32 i = 0; i < ms_aRegistrationHandles.GetSize(); ++i) + { + xnUSBUnregisterFromConnectivityEvents(ms_aRegistrationHandles[i]); + } + ms_aRegistrationHandles.Clear(); + ms_connectedEvent.Clear(); + ms_disconnectedEvent.Clear(); + + xnOSCloseCriticalSection(&ms_lock); + + xnUSBShutdown(); + + ms_devices.Clear(); + + ms_initialized = FALSE; + } +} + +void LinkDeviceEnumeration::OnConnectivityEvent(const XnChar* uri, XnUSBEventType eventType, XnUsbId usbId) +{ + xnl::AutoCSLocker lock(ms_lock); + + if (eventType == XN_USB_EVENT_DEVICE_CONNECT) + { + if (ms_devices.Find(uri) == ms_devices.End()) + { + OniDeviceInfo deviceInfo; + deviceInfo.usbVendorId = usbId.vendorID; + deviceInfo.usbProductId = usbId.productID; + xnOSStrCopy(deviceInfo.uri, uri, sizeof(deviceInfo.uri)); + xnOSStrCopy(deviceInfo.vendor, XN_VENDOR_PRIMESENSE, sizeof(deviceInfo.vendor)); + xnOSStrCopy(deviceInfo.name, "PSLink", sizeof(deviceInfo.name)); + + // add it to hash + ms_devices.Set(uri, deviceInfo); + + // raise event + ms_connectedEvent.Raise(deviceInfo); + } + } + else if (eventType == XN_USB_EVENT_DEVICE_DISCONNECT) + { + OniDeviceInfo deviceInfo; + if (XN_STATUS_OK == ms_devices.Get(uri, deviceInfo)) + { + // raise event + ms_disconnectedEvent.Raise(deviceInfo); + + // remove it + ms_devices.Remove(uri); + } + } +} + +void XN_CALLBACK_TYPE LinkDeviceEnumeration::OnConnectivityEventCallback(XnUSBEventArgs* pArgs, void* pCookie) +{ + XnUsbId usbId = *(XnUsbId*)pCookie; + OnConnectivityEvent(pArgs->strDevicePath, pArgs->eventType, usbId); +} + +OniDeviceInfo* LinkDeviceEnumeration::GetDeviceInfo(const XnChar* uri) +{ + OniDeviceInfo* pInfo = NULL; + xnl::AutoCSLocker lock(ms_lock); + + if (ms_devices.Get(uri, pInfo) == XN_STATUS_OK) + { + return pInfo; + } + else + { + return NULL; + } +} + diff --git a/Source/Drivers/PSLink/LinkDeviceEnumeration.h b/Source/Drivers/PSLink/LinkDeviceEnumeration.h new file mode 100644 index 0000000..1a5d68c --- /dev/null +++ b/Source/Drivers/PSLink/LinkDeviceEnumeration.h @@ -0,0 +1,68 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef _LINK_DEVICE_ENUMERATION_H_ +#define _LINK_DEVICE_ENUMERATION_H_ + +#include +#include +#include +#include +#include + +class LinkDeviceEnumeration +{ +public: + typedef xnl::Event1Arg DeviceConnectivityEvent; + + static XnStatus Initialize(); + static void Shutdown(); + + static OniDeviceInfo* GetDeviceInfo(const XnChar* uri); + + static DeviceConnectivityEvent::Interface& ConnectedEvent() { return ms_connectedEvent; } + static DeviceConnectivityEvent::Interface& DisconnectedEvent() { return ms_disconnectedEvent; } + + static XnStatus EnumerateSensors(OniDeviceInfo* aDevices, XnUInt32* pnCount); + +private: + typedef struct XnUsbId + { + XnUInt16 vendorID; + XnUInt16 productID; + } XnUsbId; + + typedef xnl::StringsHash DevicesHash; + + static void XN_CALLBACK_TYPE OnConnectivityEventCallback(XnUSBEventArgs* pArgs, void* pCookie); + static void OnConnectivityEvent(const XnChar* uri, XnUSBEventType eventType, XnUsbId usbId); + + static XnBool ms_initialized; + static DeviceConnectivityEvent ms_connectedEvent; + static DeviceConnectivityEvent ms_disconnectedEvent; + + static XnUsbId ms_supportedProducts[]; + static XnUInt32 ms_supportedProductsCount; + static DevicesHash ms_devices; + static xnl::Array ms_aRegistrationHandles; + static XN_CRITICAL_SECTION_HANDLE ms_lock; +}; + +#endif diff --git a/Source/Drivers/PSLink/LinkProtoLib/IAsyncInputConnection.h b/Source/Drivers/PSLink/LinkProtoLib/IAsyncInputConnection.h new file mode 100644 index 0000000..a4cc9a5 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/IAsyncInputConnection.h @@ -0,0 +1,27 @@ +#ifndef __IASYNCINPUTCONNECTION_H__ +#define __IASYNCINPUTCONNECTION_H__ + +#include "IConnection.h" +#include + +namespace xn +{ + +class IDataDestination +{ +public: + virtual ~IDataDestination() {} + virtual XnStatus IncomingData(const void* pData, XnUInt32 nSize) = 0; + virtual void HandleDisconnection() = 0; +}; + +class IAsyncInputConnection : virtual public IConnection +{ +public: + virtual ~IAsyncInputConnection() {} + virtual XnStatus SetDataDestination(IDataDestination* pDataDestination) = 0; +}; + +} + +#endif // __IASYNCINPUTCONNECTION_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/IConnection.h b/Source/Drivers/PSLink/LinkProtoLib/IConnection.h new file mode 100644 index 0000000..fc2a923 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/IConnection.h @@ -0,0 +1,20 @@ +#ifndef __ICONNECTION_H__ +#define __ICONNECTION_H__ + +#include + +namespace xn +{ + +class IConnection +{ +public: + virtual XnStatus Connect() = 0; + virtual void Disconnect() = 0; + virtual XnBool IsConnected() const = 0; + virtual XnUInt16 GetMaxPacketSize() const = 0; +}; + +} + +#endif // __ICONNECTION_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/IConnectionFactory.h b/Source/Drivers/PSLink/LinkProtoLib/IConnectionFactory.h new file mode 100644 index 0000000..81b4262 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/IConnectionFactory.h @@ -0,0 +1,34 @@ +#ifndef __ICONNECTIONFACTORY_H__ +#define __ICONNECTIONFACTORY_H__ + +#include +#include + +namespace xn +{ + +class ISyncIOConnection; +class IOutputConnection; +class IAsyncInputConnection; + +class IConnectionFactory +{ +public: + virtual ~IConnectionFactory() {} + virtual XnStatus Init(const XnChar* strConnString) = 0; + virtual void Shutdown() = 0; + virtual XnBool IsInitialized() const = 0; + virtual XnUInt16 GetNumInputDataConnections() const = 0; + virtual XnUInt16 GetNumOutputDataConnections() const = 0; + + /** The pointer returned by GetControlConnection() belongs to the connection factory and + must not be deleted by caller. **/ + virtual XnStatus GetControlConnection(ISyncIOConnection*& pConnection) = 0; + + virtual XnStatus CreateOutputDataConnection(XnUInt16 nID, IOutputConnection*& pConnection) = 0; + virtual XnStatus CreateInputDataConnection(XnUInt16 nID, IAsyncInputConnection*& pConnection) = 0; +}; + +} + +#endif // __ICONNECTIONFACTORY_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/ILinkOutputStream.h b/Source/Drivers/PSLink/LinkProtoLib/ILinkOutputStream.h new file mode 100644 index 0000000..e9275b0 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/ILinkOutputStream.h @@ -0,0 +1,40 @@ +#ifndef __ILINKOUTPUTSTREAM_H__ +#define __ILINKOUTPUTSTREAM_H__ + +#include "XnLinkProtoLibDefs.h" +#include "XnLinkProtoUtils.h" +#include +#include + +namespace xn +{ + +class LinkOutputDataEndpoint; + +class ILinkOutputStream +{ +public: + virtual ~ILinkOutputStream() {} + + virtual XnStatus Init(XnUInt16 nStreamID, + XnUInt32 nMaxMsgSize, + XnUInt16 nMaxPacketSize, + XnLinkCompressionType compression, + XnUInt16 nInitialPacketID, + LinkOutputDataEndpoint* pOutputDataEndpoint) = 0; + + virtual XnBool IsInitialized() const = 0; + virtual void Shutdown() = 0; + virtual XnLinkCompressionType GetCompression() const = 0; + + virtual XnStatus SendData(XnUInt16 nMsgType, + XnUInt16 nCID, + XnLinkFragmentation fragmentation, + const void* pData, + XnUInt32 nDataSize) const = 0; + +}; + +} + +#endif // __ILINKOUTPUTSTREAM_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/IOutputConnection.h b/Source/Drivers/PSLink/LinkProtoLib/IOutputConnection.h new file mode 100644 index 0000000..b3ca759 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/IOutputConnection.h @@ -0,0 +1,25 @@ +#ifndef __IOUTPUTCONNECTION_H__ +#define __IOUTPUTCONNECTION_H__ + +#include "IConnection.h" + +namespace xn +{ + +class IOutputConnection : virtual public IConnection +{ +public: + virtual ~IOutputConnection() {} + + /** + * Sends a buffer on the connection. + * + * @param pData [out] Buffer that holds data to send. + * @param nDataSize [in] Size of data to send. + */ + virtual XnStatus Send(const void* pData, XnUInt32 nSize) = 0; +}; + +} + +#endif // __IOUTPUTCONNECTION_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/ISyncIOConnection.h b/Source/Drivers/PSLink/LinkProtoLib/ISyncIOConnection.h new file mode 100644 index 0000000..e3bf880 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/ISyncIOConnection.h @@ -0,0 +1,38 @@ +#ifndef __ISYNCIOCONNECTION_H__ +#define __ISYNCIOCONNECTION_H__ + +#include +#include + +#include "IOutputConnection.h" +#include "ISyncInputConnection.h" + +namespace xn +{ + +class ISyncIOConnection : virtual public ISyncInputConnection, + virtual public IOutputConnection +{ +public: + virtual ~ISyncIOConnection() {} + + /** + * Receives data from the stream. + * + * @param pData [out] Buffer to hold received data. + * @param nSize [in] Size of data to receive. + */ + virtual XnStatus Receive(void* pData, XnUInt32& nSize) = 0; + + /** + * Sends a buffer on the connection. + * + * @param pData [out] Buffer that holds data to send. + * @param nDataSize [in] Size of data to send. + */ + virtual XnStatus Send(const void* pData, XnUInt32 nSize) = 0; +}; + +} + +#endif // __ISYNCIOCONNECTION_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/ISyncInputConnection.h b/Source/Drivers/PSLink/LinkProtoLib/ISyncInputConnection.h new file mode 100644 index 0000000..8df87ee --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/ISyncInputConnection.h @@ -0,0 +1,19 @@ +#ifndef __ISYNCINPUTCONNECTION_H__ +#define __ISYNCINPUTCONNECTION_H__ + +#include +#include "IConnection.h" + +namespace xn +{ + +class ISyncInputConnection : virtual public IConnection +{ +public: + virtual ~ISyncInputConnection() {} + virtual XnStatus Receive(void* pData, XnUInt32& nSize) = 0; +}; + +} + +#endif // __ISYNCINPUTCONNECTION_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/LinkProtoLibVersion.h b/Source/Drivers/PSLink/LinkProtoLib/LinkProtoLibVersion.h new file mode 100644 index 0000000..2cc0019 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/LinkProtoLibVersion.h @@ -0,0 +1,25 @@ +#ifndef __LINKPROTOLIBVERSION_H__ +#define __LINKPROTOLIBVERSION_H__ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define LINK_PROTO_LIB_MAJOR_VERSION 0 +#define LINK_PROTO_LIB_MINOR_VERSION 25 + +#define LINK_PROTO_LIB_BRIEF_VERSION_STRING \ + XN_STRINGIFY(LINK_PROTO_LIB_MAJOR_VERSION) "." \ + XN_STRINGIFY(LINK_PROTO_LIB_MINOR_VERSION) + +#define LINK_PROTO_LIB_VERSION (LINK_PROTO_LIB_MAJOR_VERSION * 100 + LINK_PROTO_LIB_MINOR_VERSION) + +#define LINK_PROTO_LIB_VERSION_STRING \ + LINK_PROTO_LIB_BRIEF_VERSION_STRING "-" \ + XN_PLATFORM_STRING " (" XN_TIMESTAMP ")" + +#endif // __LINKPROTOLIBVERSION_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.cpp new file mode 100644 index 0000000..22b40b3 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.cpp @@ -0,0 +1,34 @@ +#include "XnClientSocketInConnection.h" +#include + +#define XN_MASK_SOCKETS "xnSockets" + +namespace xn +{ + +ClientSocketInConnection::ClientSocketInConnection() +{ + +} + +ClientSocketInConnection::~ClientSocketInConnection() +{ + Shutdown(); +} + +XnStatus ClientSocketInConnection::ConnectSocket(XN_SOCKET_HANDLE& hSocket, const XnChar* strIP, XnUInt16 nPort) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSCreateSocket(XN_OS_TCP_SOCKET, strIP, nPort, &hSocket); + XN_IS_STATUS_OK_LOG_ERROR("Create input socket", nRetVal); + + xnLogVerbose(XN_MASK_SOCKETS, "Client connecting to %s:%u...", strIP, nPort); + nRetVal = xnOSConnectSocket(hSocket, CONNECT_TIMEOUT); + XN_IS_STATUS_OK_LOG_ERROR("Connect input socket", nRetVal); + xnLogVerbose(XN_MASK_SOCKETS, "Client connected to %s:%u", strIP, nPort); + + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.h b/Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.h new file mode 100644 index 0000000..0df94bd --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientSocketInConnection.h @@ -0,0 +1,20 @@ +#ifndef __XNCLIENTSOCKETINCONNECTION_H__ +#define __XNCLIENTSOCKETINCONNECTION_H__ + +#include "XnSocketInConnection.h" + +namespace xn +{ + +class ClientSocketInConnection : public SocketInConnection +{ +public: + ClientSocketInConnection(); + virtual ~ClientSocketInConnection(); +protected: + virtual XnStatus ConnectSocket(XN_SOCKET_HANDLE& hSocket, const XnChar* strIP, XnUInt16 nPort); +}; + +} + +#endif // __XNCLIENTSOCKETINCONNECTION_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientSyncSocketConnection.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnClientSyncSocketConnection.cpp new file mode 100644 index 0000000..e69de29 diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientSyncSocketConnection.h b/Source/Drivers/PSLink/LinkProtoLib/XnClientSyncSocketConnection.h new file mode 100644 index 0000000..a03e92a --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientSyncSocketConnection.h @@ -0,0 +1,11 @@ +#include "XnSyncSocketConnection.h" + +namespace xn +{ + +class ClientSyncSocketConnection : public SyncSocketConnection +{ + +}; + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.cpp new file mode 100644 index 0000000..ab3a011 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.cpp @@ -0,0 +1,195 @@ +#include "XnClientUSBConnectionFactory.h" +#include "XnClientUSBControlEndpoint.h" +#include "XnClientUSBOutDataEndpoint.h" +#include "XnClientUSBInDataEndpoint.h" +#include "XnLinkProtoUtils.h" +#include +#include + +#define XN_MASK_USB "xnUSB" + +namespace xn +{ + +const XnUInt16 ClientUSBConnectionFactory::NUM_INPUT_CONNECTIONS = 2; + +ClientUSBConnectionFactory::ClientUSBConnectionFactory(XnUInt16 nInputConnections, + XnUInt16 nOutputConnections, + XnUInt32 nPreControlReceiveSleep) : + m_nInputConnections(nInputConnections), + m_nOutputConnections(nOutputConnections), + m_nPreControlReceiveSleep(nPreControlReceiveSleep), + m_nAltInterface(0), + m_controlEndpoint(nPreControlReceiveSleep), + m_hUSBDevice(NULL), + m_bInitialized(FALSE), + m_bUsbInitialized(FALSE), + m_dataOpen(FALSE) +{ +} + +ClientUSBConnectionFactory::~ClientUSBConnectionFactory() +{ + Shutdown(); +} + +XnStatus ClientUSBConnectionFactory::Init(const XnChar* strConnString) +{ + XnStatus nRetVal = xnUSBInit(); + XN_IS_STATUS_OK_LOG_ERROR("Initialize USB", nRetVal); + m_bUsbInitialized = TRUE; + + nRetVal = xnUSBOpenDeviceByPath(strConnString, &m_hUSBDevice); + XN_IS_STATUS_OK_LOG_ERROR("Open USB device", nRetVal); + + //TODO: Check speed maybe? + nRetVal = m_controlEndpoint.Init(m_hUSBDevice); + XN_IS_STATUS_OK_LOG_ERROR("Init usb control endpoint", nRetVal); + + m_bInitialized = TRUE; + return XN_STATUS_OK; +} + +void ClientUSBConnectionFactory::Shutdown() +{ + m_controlEndpoint.Shutdown(); + + //Close USB device + if (m_hUSBDevice != NULL) + { + xnUSBCloseDevice(m_hUSBDevice); + m_hUSBDevice = NULL; + } + + if (m_bUsbInitialized) + { + xnUSBShutdown(); + m_bUsbInitialized = FALSE; + } + + m_bInitialized = FALSE; +} + +XnUInt16 ClientUSBConnectionFactory::GetNumInputDataConnections() const +{ + return m_nInputConnections; +} + +XnUInt16 ClientUSBConnectionFactory::GetNumOutputDataConnections() const +{ + return m_nOutputConnections; +} + +XnBool ClientUSBConnectionFactory::IsInitialized() const +{ + return m_bInitialized; +} + +XnStatus ClientUSBConnectionFactory::GetControlConnection(ISyncIOConnection*& pConn) +{ + if (!m_bInitialized) + { + return XN_STATUS_NOT_INIT; + } + + pConn = &m_controlEndpoint; + return XN_STATUS_OK; +} + +XnStatus ClientUSBConnectionFactory::CreateOutputDataConnection(XnUInt16 /*nID*/, IOutputConnection*& pConn) +{ + //nID is ignored because we only support one output connection in the client + XnStatus nRetVal = XN_STATUS_OK; + if (!m_bInitialized) + { + return XN_STATUS_NOT_INIT; + } + + ClientUSBOutDataEndpoint* pUSBOutDataEndpoint = XN_NEW(ClientUSBOutDataEndpoint, XN_USB_EP_ISOCHRONOUS); + XN_VALIDATE_ALLOC_PTR(pUSBOutDataEndpoint); + nRetVal = pUSBOutDataEndpoint->Init(m_hUSBDevice); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_USB, "Failed to initialize output data endpoint: %s", + xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + XN_DELETE(pUSBOutDataEndpoint); + return nRetVal; + } + + pConn = pUSBOutDataEndpoint; + return XN_STATUS_OK; +} + +XnStatus ClientUSBConnectionFactory::CreateInputDataConnection(XnUInt16 nID, IAsyncInputConnection*& pConn) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bInitialized) + { + return XN_STATUS_NOT_INIT; + } + + ClientUSBInDataEndpoint* pUSBInDataEndpoint = XN_NEW(ClientUSBInDataEndpoint); + XN_VALIDATE_ALLOC_PTR(pUSBInDataEndpoint); + nRetVal = pUSBInDataEndpoint->Init(m_hUSBDevice, nID); + + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_USB, "Failed to initialize input data endpoint %u: %s", + nID, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + XN_DELETE(pUSBInDataEndpoint); + return nRetVal; + } + pConn = pUSBInDataEndpoint; + m_dataOpen = TRUE; + + return XN_STATUS_OK; +} + +XnStatus ClientUSBConnectionFactory::EnumerateConnStrings(XnUInt16 nProductID, + XnConnectionString*& astrConnStrings, + XnUInt32& nCount) +{ + XnStatus nRetVal = xnUSBInit(); + if (nRetVal == XN_STATUS_USB_ALREADY_INIT) + nRetVal = XN_STATUS_OK; + XN_IS_STATUS_OK_LOG_ERROR("Init usb", nRetVal); + nRetVal = xnUSBEnumerateDevices(XN_VENDOR_ID, nProductID, + const_cast(&astrConnStrings), &nCount); + xnUSBShutdown(); //decrease the inner ref-counter + return nRetVal; +} + +void ClientUSBConnectionFactory::FreeConnStringsList(XnConnectionString* astrConnStrings) +{ + xnUSBFreeDevicesList(astrConnStrings); +} + +XnStatus ClientUSBConnectionFactory::SetUsbAltInterface(XnUInt8 interfaceNum) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_dataOpen) + { + xnLogWarning(XN_MASK_LINK, "Can't set interface once streaming started"); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + nRetVal = xnUSBSetInterface(m_hUSBDevice, 0, interfaceNum); + XN_IS_STATUS_OK(nRetVal); + + m_nAltInterface = interfaceNum; + + return (XN_STATUS_OK); +} + +XnStatus ClientUSBConnectionFactory::GetUsbAltInterface(XnUInt8* pInterfaceNum) const +{ + *pInterfaceNum = m_nAltInterface; + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.h b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.h new file mode 100644 index 0000000..6ec7eac --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBConnectionFactory.h @@ -0,0 +1,57 @@ +#ifndef __XNUSBCONNECTIONFACTORY_H__ +#define __XNUSBCONNECTIONFACTORY_H__ + +#include "IConnectionFactory.h" +#include "XnLinkProtoLibDefs.h" +#include "XnClientUSBControlEndpoint.h" +#include +#include + +struct XnUSBDeviceHandle; +typedef XnUSBDeviceHandle* XN_USB_DEV_HANDLE; + +namespace xn +{ + +class ClientUSBConnectionFactory : public IConnectionFactory +{ +public: + ClientUSBConnectionFactory(XnUInt16 nInputConnections, + XnUInt16 nOutputConnections, + XnUInt32 nPreControlReceiveSleep); + + virtual ~ClientUSBConnectionFactory(); + virtual XnStatus Init(const XnChar* strConnString); + virtual void Shutdown(); + virtual XnBool IsInitialized() const; + + XnStatus SetUsbAltInterface(XnUInt8 interfaceNum); + XnStatus GetUsbAltInterface(XnUInt8* pInterfaceNum) const; + + virtual XnUInt16 GetNumOutputDataConnections() const; + virtual XnUInt16 GetNumInputDataConnections() const; + + virtual XnStatus GetControlConnection(ISyncIOConnection*& pConn); + virtual XnStatus CreateOutputDataConnection(XnUInt16 nID, IOutputConnection*& pConn); + virtual XnStatus CreateInputDataConnection(XnUInt16 nID, IAsyncInputConnection*& pConn); + + static XnStatus EnumerateConnStrings(XnUInt16 nProductID, XnConnectionString*& astrConnStrings, XnUInt32& nCount); + static void FreeConnStringsList(XnConnectionString* astrConnStrings); + +private: + XnUInt16 m_nInputConnections; + XnUInt16 m_nOutputConnections; + XnUInt32 m_nPreControlReceiveSleep; + XnUInt8 m_nAltInterface; + + ClientUSBControlEndpoint m_controlEndpoint; + static const XnUInt16 NUM_INPUT_CONNECTIONS; + XN_USB_DEV_HANDLE m_hUSBDevice; + XnBool m_bInitialized; + XnBool m_bUsbInitialized; + XnBool m_dataOpen; +}; + +} + +#endif // __XNUSBCONNECTIONFACTORY_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.cpp new file mode 100644 index 0000000..3ec2907 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.cpp @@ -0,0 +1,90 @@ +#include "XnClientUSBControlEndpoint.h" +#include +#include +#include + +#define XN_MASK_USB "xnUSB" + +namespace xn +{ + + +/*USB control endpoints' REAL max packet size is always 64 bytes. We group a few of these together, so we + treat the packet size as a different (larger) number.*/ +const XnUInt32 ClientUSBControlEndpoint::USB_LOW_LEVEL_MAX_PACKET_SIZE = 64; +const XnUInt32 ClientUSBControlEndpoint::SEND_TIMEOUT = 5000; +const XnUInt32 ClientUSBControlEndpoint::RECEIVE_TIMEOUT = 5000; + +ClientUSBControlEndpoint::ClientUSBControlEndpoint(XnUInt32 nPreControlReceiveSleep) +{ + m_hUSBDevice = NULL; + m_nPreControlReceiveSleep = nPreControlReceiveSleep; +} + +ClientUSBControlEndpoint::~ClientUSBControlEndpoint() +{ + Shutdown(); +} + +XnStatus ClientUSBControlEndpoint::Init(XN_USB_DEV_HANDLE hUSBDevice) +{ + XN_VALIDATE_INPUT_PTR(hUSBDevice); + m_hUSBDevice = hUSBDevice; + + return XN_STATUS_OK; +} + +void ClientUSBControlEndpoint::Shutdown() +{ + m_hUSBDevice = NULL; +} + +XnStatus ClientUSBControlEndpoint::Connect() +{ + //Nothing to do here since the control endpoint is always connected when the device is open + return XN_STATUS_OK; +} + +void ClientUSBControlEndpoint::Disconnect() +{ +} + + +XnUInt16 ClientUSBControlEndpoint::GetMaxPacketSize() const +{ + XN_ASSERT(FALSE); //Did you mean the logical packet size?? If the answer is no, remove this assert. + return USB_LOW_LEVEL_MAX_PACKET_SIZE; +} + +XnStatus ClientUSBControlEndpoint::Receive(void* pData, XnUInt32& nSize) +{ + XnUInt32 nBufferSize = nSize; + XnStatus nRetVal = XN_STATUS_OK; + + // Workaround devices bug: in some devices (Lena for example), one cannot receive + // immediately after send. The in-request should arrive AFTER the device has + // finished reading the entire out-request and its data, and clear the state. + // otherwise, device stalls. + xnOSSleep(m_nPreControlReceiveSleep); + + nRetVal = xnUSBReceiveControl(m_hUSBDevice, XN_USB_CONTROL_TYPE_VENDOR, 0, 0, 0, + reinterpret_cast(pData), nBufferSize, &nSize, RECEIVE_TIMEOUT); + XN_IS_STATUS_OK_LOG_ERROR("Receive buffer from USB", nRetVal); + + return XN_STATUS_OK; +} + +XnStatus ClientUSBControlEndpoint::Send(const void* pData, XnUInt32 nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = xnUSBSendControl(m_hUSBDevice, XN_USB_CONTROL_TYPE_VENDOR, 0, 0, 0, (XnUInt8*)pData, nSize, SEND_TIMEOUT); + XN_IS_STATUS_OK_LOG_ERROR("Send USB control data", nRetVal); + return XN_STATUS_OK; +} + +XnBool ClientUSBControlEndpoint::IsConnected() const +{ + return TRUE; +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.h new file mode 100644 index 0000000..327b043 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBControlEndpoint.h @@ -0,0 +1,45 @@ +#ifndef __XNUSBCONTROLENDPOINT_H__ +#define __XNUSBCONTROLENDPOINT_H__ + +#include "ISyncIOConnection.h" + +struct XnUSBDeviceHandle; +typedef XnUSBDeviceHandle* XN_USB_DEV_HANDLE; + +namespace xn +{ + +class ClientUSBControlEndpoint : virtual public ISyncIOConnection +{ +public: + ClientUSBControlEndpoint(XnUInt32 nPreControlReceiveSleep); + virtual ~ClientUSBControlEndpoint(); + // Operations + XnStatus Init(XN_USB_DEV_HANDLE hUSBDevice); + void Shutdown(); + + + // ISyncIOConnection implementation + virtual XnStatus Connect(); + virtual void Disconnect(); + virtual XnBool IsConnected() const; + virtual XnUInt16 GetMaxPacketSize() const; + + //nSize is max size on input, actual size on output + virtual XnStatus Receive(void* pData, XnUInt32& nSize); + virtual XnStatus Send(const void* pData, XnUInt32 nSize); + +private: + //Low level usb packet size - not to be confused with our "logical" packet size which is bigger + static const XnUInt32 USB_LOW_LEVEL_MAX_PACKET_SIZE; + + static const XnUInt32 SEND_TIMEOUT; + static const XnUInt32 RECEIVE_TIMEOUT; + + XN_USB_DEV_HANDLE m_hUSBDevice; + XnUInt32 m_nPreControlReceiveSleep; +}; + +} + +#endif // __XNUSBCONTROLENDPOINT_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.cpp new file mode 100644 index 0000000..74c6f2c --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.cpp @@ -0,0 +1,152 @@ +#include "XnClientUSBInDataEndpoint.h" +#include "XnLinkProtoLibDefs.h" +#include +#include + +#define XN_MASK_USB "xnUSB" + +namespace xn +{ + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_BUFFER_NUM_PACKETS_ISO = 80; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_NUM_BUFFERS_ISO = 8; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_TIMEOUT_ISO = 100; + + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_BUFFER_NUM_PACKETS_BULK = 120; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_NUM_BUFFERS_BULK = 8; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_TIMEOUT_BULK = 1000; +#elif (XN_PLATFORM == XN_PLATFORM_LINUX_X86 || XN_PLATFORM == XN_PLATFORM_LINUX_ARM || XN_PLATFORM == XN_PLATFORM_MACOSX || XN_PLATFORM == XN_PLATFORM_ANDROID_ARM) + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_BUFFER_NUM_PACKETS_ISO = 32; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_NUM_BUFFERS_ISO = 16; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_TIMEOUT_ISO = 100; + + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_BUFFER_NUM_PACKETS_BULK = 32; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_NUM_BUFFERS_BULK = 16; + const XnUInt32 ClientUSBInDataEndpoint::READ_THREAD_TIMEOUT_BULK = 1000; +#else + #error "Unsupported platform :(" +#endif + +const XnUInt32 ClientUSBInDataEndpoint::BASE_INPUT_ENDPOINT = XN_EE_DEVICE_IN_DATA_BASE_ENDPOINT; + +ClientUSBInDataEndpoint::ClientUSBInDataEndpoint() +{ + m_hUSBDevice = NULL; + m_nEndpointID = 0; + m_nMaxPacketSize = 0; + m_pDataDestination = NULL; + m_bConnected = FALSE; + m_hEndpoint = NULL; +} + +ClientUSBInDataEndpoint::~ClientUSBInDataEndpoint() +{ + Shutdown(); +} + +XnStatus ClientUSBInDataEndpoint::Init(XN_USB_DEV_HANDLE hUSBDevice, XnUInt16 nEndpointID) +{ + XN_VALIDATE_INPUT_PTR(hUSBDevice); + XnStatus nRetVal = XN_STATUS_OK; + m_hUSBDevice = hUSBDevice; + m_nEndpointID = BASE_INPUT_ENDPOINT + nEndpointID; + m_endpointType = XN_USB_EP_ISOCHRONOUS; + nRetVal = xnUSBOpenEndPoint(m_hUSBDevice, m_nEndpointID, m_endpointType, XN_USB_DIRECTION_IN, &m_hEndpoint); + if (nRetVal == XN_STATUS_USB_WRONG_ENDPOINT_TYPE) + { + m_endpointType = XN_USB_EP_BULK; + nRetVal = xnUSBOpenEndPoint(m_hUSBDevice, m_nEndpointID, m_endpointType, XN_USB_DIRECTION_IN, &m_hEndpoint); + } + XN_IS_STATUS_OK_LOG_ERROR("Open USB endpoint", nRetVal); + XnUInt32 nTempMaxPacketSize = 0; + nRetVal = xnUSBGetEndPointMaxPacketSize(m_hEndpoint, &nTempMaxPacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Get USB endpoint max packet size", nRetVal); + if (nTempMaxPacketSize > XN_MAX_UINT16) + { + xnLogError(XN_MASK_USB, "Max packet size received is larger than max uint16 value?!"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + m_nMaxPacketSize = static_cast(nTempMaxPacketSize); + + return XN_STATUS_OK; +} + +void ClientUSBInDataEndpoint::Shutdown() +{ + Disconnect(); + xnUSBCloseEndPoint(m_hEndpoint); + m_hEndpoint = NULL; + m_hUSBDevice = NULL; +} + +XnStatus ClientUSBInDataEndpoint::Connect() +{ + XnStatus nRetVal = XN_STATUS_OK; + Disconnect(); //In case we were connected already + + if (!m_bConnected) + { + XnUInt32 nBufferSize = m_nMaxPacketSize * ((m_endpointType == XN_USB_EP_ISOCHRONOUS) ? READ_THREAD_BUFFER_NUM_PACKETS_ISO : READ_THREAD_BUFFER_NUM_PACKETS_BULK); + XnUInt32 nBuffersCount = (m_endpointType == XN_USB_EP_ISOCHRONOUS) ? READ_THREAD_NUM_BUFFERS_ISO : READ_THREAD_NUM_BUFFERS_BULK; + XnUInt32 nTimeout = (m_endpointType == XN_USB_EP_ISOCHRONOUS) ? READ_THREAD_TIMEOUT_ISO : READ_THREAD_TIMEOUT_BULK; + nRetVal = xnUSBInitReadThread(m_hEndpoint, nBufferSize, nBuffersCount, nTimeout, ReadThreadCallback, this); + XN_IS_STATUS_OK_LOG_ERROR("Init USB Read thread", nRetVal); + m_bConnected = TRUE; + } + return XN_STATUS_OK; +} + +void ClientUSBInDataEndpoint::Disconnect() +{ + XnStatus nRetVal = XN_STATUS_OK; + if (m_bConnected) + { + nRetVal = xnUSBShutdownReadThread(m_hEndpoint); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning("Failed to shutdown usb read thread: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } + m_bConnected = FALSE; + } +} + +XnUInt16 ClientUSBInDataEndpoint::GetMaxPacketSize() const +{ + return m_nMaxPacketSize; +} + +XnStatus ClientUSBInDataEndpoint::SetDataDestination(IDataDestination* pDataDestination) +{ + XN_VALIDATE_INPUT_PTR(pDataDestination); + m_pDataDestination = pDataDestination; + return XN_STATUS_OK; +} + +XnBool XN_CALLBACK_TYPE ClientUSBInDataEndpoint::ReadThreadCallback(XnUChar* pBuffer, XnUInt32 nBufferSize, void* pCallbackData) +{ + ClientUSBInDataEndpoint* pThis = reinterpret_cast(pCallbackData); + IDataDestination* pDataDestination = pThis->m_pDataDestination; + if (pDataDestination != NULL) + { + if (nBufferSize == 0) + { + //xnLogVerbose(XN_MASK_USB, "USB In Data Endpoint Got 0 length data"); + } + else + { + pDataDestination->IncomingData(pBuffer, nBufferSize); + } + } + + return TRUE; +} + +XnBool ClientUSBInDataEndpoint::IsConnected() const +{ + return m_bConnected; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.h new file mode 100644 index 0000000..003cd4b --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBInDataEndpoint.h @@ -0,0 +1,57 @@ +#ifndef __XNUSBINDATAENDPOINT_H__ +#define __XNUSBINDATAENDPOINT_H__ + +#include "IAsyncInputConnection.h" +#include + +struct XnUSBDeviceHandle; +struct XnUSBEndPointHandle; + +typedef XnUSBDeviceHandle* XN_USB_DEV_HANDLE; +typedef XnUSBEndPointHandle* XN_USB_EP_HANDLE; + +namespace xn +{ + +class ClientUSBInDataEndpoint : virtual public IAsyncInputConnection +{ +public: + ClientUSBInDataEndpoint(); + + virtual ~ClientUSBInDataEndpoint(); + + virtual XnStatus Init(XN_USB_DEV_HANDLE hUSBDevice, XnUInt16 nEndpointID); + virtual void Shutdown(); + virtual XnStatus Connect(); + virtual void Disconnect(); + virtual XnBool IsConnected() const; + virtual XnUInt16 GetMaxPacketSize() const; + virtual XnStatus SetDataDestination(IDataDestination* pDataDestination); + + inline XnUSBEndPointType GetEndpointType() const { return m_endpointType; } + +private: + static XnBool XN_CALLBACK_TYPE ReadThreadCallback(XnUChar* pBuffer, XnUInt32 nBufferSize, void* pCallbackData); + + static const XnUInt32 READ_THREAD_BUFFER_NUM_PACKETS_ISO; + static const XnUInt32 READ_THREAD_NUM_BUFFERS_ISO; + static const XnUInt32 READ_THREAD_TIMEOUT_ISO; + static const XnUInt32 READ_THREAD_BUFFER_NUM_PACKETS_BULK; + static const XnUInt32 READ_THREAD_NUM_BUFFERS_BULK; + static const XnUInt32 READ_THREAD_TIMEOUT_BULK; + + static const XnUInt32 BASE_INPUT_ENDPOINT; + + XnUSBEndPointType m_endpointType; + XN_USB_EP_HANDLE m_hEndpoint; + XN_USB_DEV_HANDLE m_hUSBDevice; + XnUInt16 m_nEndpointID; + XnUInt16 m_nMaxPacketSize; + IDataDestination* m_pDataDestination; + XnBool m_bConnected; + +}; + +} + +#endif // __XNUSBINDATAENDPOINT_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.cpp new file mode 100644 index 0000000..e11d994 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.cpp @@ -0,0 +1,110 @@ +#include "XnClientUSBOutDataEndpoint.h" +#include +#include + +#define XN_MASK_USB "xnUSB" + +namespace xn +{ + +const XnUInt16 ClientUSBOutDataEndpoint::ENDPOINT_ID = 0x0001; +const XnUInt32 ClientUSBOutDataEndpoint::SEND_TIMEOUT = 2000; + +ClientUSBOutDataEndpoint::ClientUSBOutDataEndpoint(XnUSBEndPointType endpointType) +{ + m_hEndpoint = NULL; + m_hUSBDevice = NULL; + m_nMaxPacketSize = 0; + m_endpointType = endpointType; + m_bConnected = FALSE; +} + +ClientUSBOutDataEndpoint::~ClientUSBOutDataEndpoint() +{ + Shutdown(); +} + +XnStatus ClientUSBOutDataEndpoint::Init(XN_USB_DEV_HANDLE hUSBDevice) +{ + XN_VALIDATE_INPUT_PTR(hUSBDevice); + m_hUSBDevice = hUSBDevice; + return XN_STATUS_OK; +} + +void ClientUSBOutDataEndpoint::Shutdown() +{ + Disconnect(); + m_hUSBDevice = NULL; +} + +XnStatus ClientUSBOutDataEndpoint::Connect() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bConnected) + { + nRetVal = xnUSBOpenEndPoint(m_hUSBDevice, ENDPOINT_ID, m_endpointType, XN_USB_DIRECTION_OUT, &m_hEndpoint); + XN_IS_STATUS_OK_LOG_ERROR("Open USB Out Data Endpoint", nRetVal); + XnUInt32 nTempMaxPacketSize = 0; + nRetVal = xnUSBGetEndPointMaxPacketSize(m_hEndpoint, &nTempMaxPacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Get USB Out Data endpoint max packet size", nRetVal); + if (nTempMaxPacketSize > XN_MAX_UINT16) + { + xnLogError(XN_MASK_USB, "Max packet size exceeds max uint16 value ?!"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + m_nMaxPacketSize = static_cast(nTempMaxPacketSize); + m_bConnected = TRUE; + } + return XN_STATUS_OK; +} + +void ClientUSBOutDataEndpoint::Disconnect() +{ + if (m_bConnected) + { + xnUSBCloseEndPoint(m_hEndpoint); + m_hEndpoint = NULL; + } +} + +XnStatus ClientUSBOutDataEndpoint::Send(const void* pData, XnUInt32 nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + /* TEMP TEMP TEMP - Patch to bypass USB driver bug */ + { + XnUInt32 nBlockSize = 8 * m_nMaxPacketSize; + XnUInt32 nRemainderSize = nSize % nBlockSize; + if (nRemainderSize > 0) + { + xnLogVerbose(XN_MASK_USB, "Temporary USB patch: rounded up size to %u (instead of %u) before sending data", nSize + nBlockSize - nRemainderSize, nSize); + // memset rest of buffer (otherwise it will contain old headers) + xnOSMemSet((XnUInt8*)pData + nSize, 0, nBlockSize - nRemainderSize); + nSize += (nBlockSize - nRemainderSize); + } + } + /* TEMP TEMP TEMP - Patch to bypass USB driver bug */ + + nRetVal = xnUSBWriteEndPoint(m_hEndpoint, (XnUChar*)pData, nSize, SEND_TIMEOUT); + + /* TEMP TEMP TEMP - Patch - prevent USB driver buffer from overflowing */ + //xnOSSleep(2000); + /* TEMP TEMP TEMP - Patch - prevent USB driver buffer from overflowing */ + + XN_IS_STATUS_OK_LOG_ERROR("Write to USB data endpoint", nRetVal); + return XN_STATUS_OK; +} + +XnUInt16 ClientUSBOutDataEndpoint::GetMaxPacketSize() const +{ + XN_ASSERT(m_hEndpoint != NULL); //Are we even connected? + return m_nMaxPacketSize; +} + +XnBool ClientUSBOutDataEndpoint::IsConnected() const +{ + return m_bConnected; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.h new file mode 100644 index 0000000..d1bdbf9 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnClientUSBOutDataEndpoint.h @@ -0,0 +1,43 @@ +#ifndef __XNUSBOUTDATAENDPOINT_H__ +#define __XNUSBOUTDATAENDPOINT_H__ + +#include "IOutputConnection.h" +#include + +struct XnUSBDeviceHandle; +struct XnUSBEndPointHandle; + +typedef XnUSBDeviceHandle* XN_USB_DEV_HANDLE; +typedef XnUSBEndPointHandle* XN_USB_EP_HANDLE; + +namespace xn +{ + +class ClientUSBOutDataEndpoint : virtual public IOutputConnection +{ +public: + ClientUSBOutDataEndpoint(XnUSBEndPointType endpointType); + virtual ~ClientUSBOutDataEndpoint(); + + virtual XnStatus Init(XN_USB_DEV_HANDLE hUSBDevice); + virtual void Shutdown(); + virtual XnStatus Connect(); + virtual void Disconnect(); + virtual XnBool IsConnected() const; + virtual XnStatus Send(const void* pData, XnUInt32 nSize); + virtual XnUInt16 GetMaxPacketSize() const; + +private: + XnUSBEndPointType m_endpointType; + XN_USB_EP_HANDLE m_hEndpoint; + XN_USB_DEV_HANDLE m_hUSBDevice; + static const XnUInt16 ENDPOINT_ID; + static const XnUInt32 SEND_TIMEOUT; + + XnUInt16 m_nMaxPacketSize; + XnBool m_bConnected; +}; + +} + +#endif // __XNUSBOUTDATAENDPOINT_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnCyclicBuffer.h b/Source/Drivers/PSLink/LinkProtoLib/XnCyclicBuffer.h new file mode 100644 index 0000000..31cfdc5 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnCyclicBuffer.h @@ -0,0 +1,123 @@ +#ifndef __XNCYCLICBUFFER_H__ +#define __XNCYCLICBUFFER_H__ + +#include + +namespace xn +{ + +class CyclicBuffer +{ +public: + CyclicBuffer() + { + m_pBuffer = NULL; + m_nBegin = 0; + m_nEnd = 0; + m_nBufferSize = 0; + } + + ~CyclicBuffer() + { + Shutdown(); + } + + XnStatus Init(XnUInt32 nMaxSize) + { + //Allocate +1 byte for end marker + m_nBufferSize = nMaxSize + 1; + m_pBuffer = reinterpret_cast(xnOSMallocAligned(m_nBufferSize, XN_DEFAULT_MEM_ALIGN)); + XN_VALIDATE_ALLOC_PTR(m_pBuffer); + m_nBegin = 0; + m_nEnd = 0; + return XN_STATUS_OK; + } + + void Shutdown() + { + XN_ALIGNED_FREE_AND_NULL(m_pBuffer); + } + + XnBool IsFull() const + { + return (((m_nEnd + 1) % m_nBufferSize) == m_nBegin); + } + + XnStatus Add(const XnUInt8* pSrc, XnUInt32 nSrcSize) + { + if (nSrcSize > m_nBufferSize - 1) + { + return XN_STATUS_INPUT_BUFFER_OVERFLOW; + } + + //Make more room for new data by moving the beginning of the buffer forward if needed. + XnUInt32 nMissingSpace = XN_MAX((XnInt32)(nSrcSize - m_nBufferSize + GetSize()), 0); + m_nBegin = ((m_nBegin + nMissingSpace) % m_nBufferSize); + //First copy either whole source or as much of it that fits between m_nEnd and buffer end. + //Then we copy the remaining bytes (if any) to the beginning of the buffer. + XnUInt32 nSize1 = XN_MIN(nSrcSize, m_nBufferSize - m_nEnd); + XnUInt32 nSize2 = (nSrcSize - nSize1); + xnOSMemCopy(m_pBuffer + m_nEnd, pSrc, nSize1); + xnOSMemCopy(m_pBuffer, pSrc + nSize1, nSize2); + m_nEnd = (m_nEnd + nSrcSize) % m_nBufferSize; + return XN_STATUS_OK; + } + + //nDestSize is max size on input, actual size on output. + XnStatus Flush(XnUInt8* pDest, XnUInt32& nDestSize) + { + XnInt32 nDiff = (m_nEnd - m_nBegin); + if (nDiff > 0) + { + if (XnUInt32(nDiff) > nDestSize) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(pDest, m_pBuffer + m_nBegin, nDiff); + nDestSize = nDiff; + } + else if (nDiff < 0) + { + if ((m_nBufferSize + nDiff) > nDestSize) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(pDest, m_pBuffer + m_nBegin, m_nBufferSize - m_nBegin); + xnOSMemCopy(pDest + m_nBufferSize - m_nBegin, m_pBuffer, m_nEnd); + nDestSize = m_nBufferSize + nDiff; + } + else + { + nDestSize = 0; + } + m_nBegin = 0; + m_nEnd = 0; + + return XN_STATUS_OK; + } + + XnUInt32 GetSize() const + { + XnInt32 nDiff = (m_nEnd - m_nBegin); + if (nDiff >= 0) + { + return nDiff; + } + else + { + return m_nBufferSize + nDiff; + } + } + +private: + XnUInt8* m_pBuffer; + XnUInt32 m_nBufferSize; + XnUInt32 m_nBegin; + XnUInt32 m_nEnd; +}; + +} + +#endif // __XNCYCLICBUFFER_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.cpp new file mode 100644 index 0000000..225516e --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.cpp @@ -0,0 +1,126 @@ +#include "XnLink11BitS2DParser.h" +#include "XnShiftToDepth.h" +#include "XnLinkProtoUtils.h" +#include + +namespace xn +{ + +Link11BitS2DParser::Link11BitS2DParser(const XnShiftToDepthTables& shiftToDepthTables) : + m_nState(0), + m_nShift(0), + m_pShiftToDepth(shiftToDepthTables.pShiftToDepthTable) +{ +} + +Link11BitS2DParser::~Link11BitS2DParser() +{ +} + +XnStatus Link11BitS2DParser::ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + XN_ASSERT(m_pShiftToDepth != NULL); + OniDepthPixel*& pDstPixel = reinterpret_cast(pDst); + const OniDepthPixel* pDstPixelEnd = reinterpret_cast(pDstEnd); + XnSizeT nPacketBits = 0; + XnSizeT nPacketDstWords = 0; + + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + + if ((fragmentation & XN_LINK_FRAG_BEGIN) != 0) + { + //Reset state for new frame + m_nState = 0; + } + + nPacketBits = (nPacketDataSize * 8); + nPacketDstWords = (nPacketBits / 11); + if ((nPacketBits % 11) != 0) + { + nPacketDstWords++; + } + + if ((pDstPixel + nPacketDstWords) > pDstPixelEnd) //Do we have enough room for this packet? + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + while (pSrc < pSrcEnd) + { + XN_ASSERT(pDstPixel < pDstPixelEnd); + switch (m_nState) + { + case 0: + m_nShift = (*pSrc << 3); //8 first bits, make room for 3 more + m_nState++; + break; + case 1: + m_nShift |= ((*pSrc >> 5) & 0x07); //3 more bits, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; //Write + m_nShift = ((*pSrc & 0x1F) << 6); //5 first bits, make room for 6 more + m_nState++; + break; + case 2: + m_nShift |= ((*pSrc >> 2) & 0x3F); //6 more bits, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; + m_nShift = ((*pSrc & 0x03) << 9); //2 first bits, make room for 9 more + m_nState++; + break; + case 3: + m_nShift |= (*pSrc << 1); //8 more bits, make room for 1 more + m_nState++; + break; + case 4: + m_nShift |= ((*pSrc >> 7) & 0x01); //1 more bit, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; + m_nShift = ((*pSrc & 0x7F) << 4); //7 first bits, make room for 4 more + m_nState++; + break; + case 5: + m_nShift |= ((*pSrc >> 4) & 0x0F); //4 more bits, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; + m_nShift = ((*pSrc & 0x0F) << 7); //4 first bits, make room for 7 more + m_nState++; + break; + case 6: + m_nShift |= ((*pSrc >> 1) & 0x7F); //7 more bits, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; + m_nShift = ((*pSrc & 0x01) << 10); //1 first bit, make room for 10 more + m_nState++; + break; + case 7: + m_nShift |= (*pSrc << 2); //8 more bits, make room for 2 more + m_nState++; + break; + case 8: + m_nShift |= ((*pSrc >> 6) & 0x03); //2 more bits, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; + m_nShift = ((*pSrc & 0x3F) << 5); //6 first bits, make room for 5 more + m_nState++; + break; + case 9: + m_nShift |= ((*pSrc >> 3) & 0x1F); //5 more bits, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; + m_nShift = ((*pSrc & 0x07) << 8); //3 first bits, make room for 8 more + m_nState++; + break; + case 10: + m_nShift |= (*pSrc); //8 more bits, we got a whole shift value + *pDstPixel++ = m_pShiftToDepth[m_nShift]; + m_nState = 0; + break; + default: + XN_ASSERT(FALSE); + }//switch + pSrc++; + }// while + + return XN_STATUS_OK; +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.h new file mode 100644 index 0000000..e46c025 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink11BitS2DParser.h @@ -0,0 +1,31 @@ +#ifndef __XNLINK11BITS2DPARSER_H__ +#define __XNLINK11BITS2DPARSER_H__ + +#include "XnLinkMsgParser.h" +#include "XnShiftToDepth.h" + +namespace xn +{ + +class Link11BitS2DParser : public LinkMsgParser +{ +public: + Link11BitS2DParser(const XnShiftToDepthTables& shiftToDepthTables); + virtual ~Link11BitS2DParser(); + +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + +private: + XnUInt32 m_nState; + XnUInt16 m_nShift; + const OniDepthPixel* m_pShiftToDepth; +}; + +} + +#endif // __XNLINK11BITS2DPARSER_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.cpp new file mode 100644 index 0000000..403af41 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.cpp @@ -0,0 +1,264 @@ +#include "XnLink12BitS2DParser.h" +#include "XnShiftToDepth.h" +#include "XnLinkProtoUtils.h" +#include + +#ifdef XN_NEON +#include +#endif + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +/* Returns a set of bits. For example XN_ON_BITS(4) returns 0xF */ +#define XN_ON_BITS(count) ((1 << count)-1) + +/* Creates a mask of bits in offset */ +#define XN_CREATE_MASK(count, offset) (XN_ON_BITS(count) << offset) + +/* Takes the bits in offset from . +* For example: +* If we want 3 bits located in offset 2 from 0xF4: +* 11110100 +* --- +* we get 101, which is 0x5. +* and so, XN_TAKE_BITS(0xF4,3,2) == 0x5. +*/ +#define XN_TAKE_BITS(source, count, offset) ((source & XN_CREATE_MASK(count, offset)) >> offset) + +namespace xn +{ + +Link12BitS2DParser::Link12BitS2DParser(const XnShiftToDepthTables& shiftToDepthTables) : + m_pShiftToDepth(shiftToDepthTables.pShiftToDepthTable) +{ +} + +Link12BitS2DParser::~Link12BitS2DParser() +{ +} + +XnStatus Link12BitS2DParser::ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + XN_ASSERT(m_pShiftToDepth != NULL); + OniDepthPixel*& pDstPixel = reinterpret_cast(pDst); + const OniDepthPixel* pDstPixelEnd = reinterpret_cast(pDstEnd); + + if ((fragmentation & XN_LINK_FRAG_BEGIN) != 0) + { + //Reset state for new frame + m_ContinuousBufferSize=0; + } + + XnUInt32 bytesWritten = ProcessFramePacketChunk(pSrc,pDst,(XnUInt32)(pSrcEnd - pSrc)); + + pDstPixel += (OniDepthPixel)(bytesWritten/2); //progress pDst by the number of pixels (bytes divided by two) + if (pDstPixel > pDstPixelEnd) //Do we have enough room for this packet? + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + return XN_STATUS_OK; +} + +XnUInt32 Link12BitS2DParser::ProcessFramePacketChunk(const XnUInt8* pData,XnUInt8* pDest, XnUInt32 nDataSize) +{ + + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 totalRead = 0; + XnUInt32 totalWrite = 0; + + // check if we have data from previous packet + if (m_ContinuousBufferSize!= 0) + { + // fill in to a whole element + XnUInt32 nReadBytes = XN_MIN(nDataSize, XN_INPUT_ELEMENT_SIZE - m_ContinuousBufferSize); + + xnOSMemCopy(m_ContinuousBuffer + m_ContinuousBufferSize, pData, nReadBytes); + m_ContinuousBufferSize += nReadBytes; + + pData += nReadBytes; + nDataSize -= nReadBytes; + + if (m_ContinuousBufferSize == XN_INPUT_ELEMENT_SIZE) + { + // process it + XnUInt32 nActualRead = 0; + XnUInt32 nActualWritten = 0; + Unpack12to16(m_ContinuousBuffer,pDest, XN_INPUT_ELEMENT_SIZE, &nActualRead, &nActualWritten); + pDest += nActualWritten; + totalRead += nActualRead; + totalWrite += nActualWritten; + m_ContinuousBufferSize = 0; + } + } + + // find out the number of input elements we have + XnUInt32 nActualRead = 0; + XnUInt32 nActualWritten = 0; + nRetVal = Unpack12to16(pData, pDest, nDataSize, &nActualRead, &nActualWritten); + totalRead += nActualRead; + totalWrite += nActualWritten; + if (nRetVal == XN_STATUS_OK) + { + pData += nActualRead; + nDataSize -= nActualRead; + + // if we have any bytes left, store them for next packet. + if (nDataSize > 0) + { + // no need to check for overflow. there can not be a case in which more than XN_INPUT_ELEMENT_SIZE + // are left. + + xnOSMemCopy(m_ContinuousBuffer + m_ContinuousBufferSize, pData, nDataSize); + m_ContinuousBufferSize += nDataSize; + } + } + return totalWrite; //return total written bytes +} + +XnStatus Link12BitS2DParser::Unpack12to16(const XnUInt8* pcInput,XnUInt8* pDest, const XnUInt32 nInputSize, XnUInt32* pnActualRead, XnUInt32* pnActualWritten) +{ + const XnUInt8* pOrigInput = (XnUInt8*)pcInput; + + XnUInt32 nElements = nInputSize / XN_INPUT_ELEMENT_SIZE; // floored + //XnUInt32 nNeededOutput = nElements * XN_OUTPUT_ELEMENT_SIZE; + + *pnActualRead = 0; + + XnUInt16 *pnOutput = (XnUInt16*)pDest; + XnUInt16 shift[16]; +#ifdef XN_NEON + XnUInt16 depth[16]; + uint8x8x3_t inD3; + uint8x8_t rshft4D, lshft4D; + uint16x8_t rshft4Q, lshft4Q; + uint16x8_t depthQ; + uint16x8x2_t shiftQ2; +#endif + + // Convert the 11bit packed data into 16bit shorts + for (XnUInt32 nElem = 0; nElem < nElements; ++nElem) + { +#ifndef XN_NEON + // input: 0, 1,2,3, 4,5,6, 7,8,9, 10,11,12, 13,14,15, 16,17,18, 19,20,21, 22,23 + // -,---,-,-,---,-,-,---,-,-,---,--,--,---,--,--,---,--,--,---,--,--,---,-- + // bits: 8,4,4,8,8,4,4,8,8,4,4,8,8,4,4, 8, 8,4,4, 8, 8,4,4, 8, 8,4,4, 8, 8,4,4, 8 + // ---,---,---,---,---,---,---,----,----,----,----,----,----,----,----,---- + // output: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + + shift[0] = (XN_TAKE_BITS(pcInput[0],8,0) << 4) | XN_TAKE_BITS(pcInput[1],4,4); + shift[1] = (XN_TAKE_BITS(pcInput[1],4,0) << 8) | XN_TAKE_BITS(pcInput[2],8,0); + shift[2] = (XN_TAKE_BITS(pcInput[3],8,0) << 4) | XN_TAKE_BITS(pcInput[4],4,4); + shift[3] = (XN_TAKE_BITS(pcInput[4],4,0) << 8) | XN_TAKE_BITS(pcInput[5],8,0); + shift[4] = (XN_TAKE_BITS(pcInput[6],8,0) << 4) | XN_TAKE_BITS(pcInput[7],4,4); + shift[5] = (XN_TAKE_BITS(pcInput[7],4,0) << 8) | XN_TAKE_BITS(pcInput[8],8,0); + shift[6] = (XN_TAKE_BITS(pcInput[9],8,0) << 4) | XN_TAKE_BITS(pcInput[10],4,4); + shift[7] = (XN_TAKE_BITS(pcInput[10],4,0) << 8) | XN_TAKE_BITS(pcInput[11],8,0); + shift[8] = (XN_TAKE_BITS(pcInput[12],8,0) << 4) | XN_TAKE_BITS(pcInput[13],4,4); + shift[9] = (XN_TAKE_BITS(pcInput[13],4,0) << 8) | XN_TAKE_BITS(pcInput[14],8,0); + shift[10] = (XN_TAKE_BITS(pcInput[15],8,0) << 4) | XN_TAKE_BITS(pcInput[16],4,4); + shift[11] = (XN_TAKE_BITS(pcInput[16],4,0) << 8) | XN_TAKE_BITS(pcInput[17],8,0); + shift[12] = (XN_TAKE_BITS(pcInput[18],8,0) << 4) | XN_TAKE_BITS(pcInput[19],4,4); + shift[13] = (XN_TAKE_BITS(pcInput[19],4,0) << 8) | XN_TAKE_BITS(pcInput[20],8,0); + shift[14] = (XN_TAKE_BITS(pcInput[21],8,0) << 4) | XN_TAKE_BITS(pcInput[22],4,4); + shift[15] = (XN_TAKE_BITS(pcInput[22],4,0) << 8) | XN_TAKE_BITS(pcInput[23],8,0); + + pnOutput[0] = m_pShiftToDepth[(shift[0])]; + pnOutput[1] = m_pShiftToDepth[(shift[1])]; + pnOutput[2] = m_pShiftToDepth[(shift[2])]; + pnOutput[3] = m_pShiftToDepth[(shift[3])]; + pnOutput[4] = m_pShiftToDepth[(shift[4])]; + pnOutput[5] = m_pShiftToDepth[(shift[5])]; + pnOutput[6] = m_pShiftToDepth[(shift[6])]; + pnOutput[7] = m_pShiftToDepth[(shift[7])]; + pnOutput[8] = m_pShiftToDepth[(shift[8])]; + pnOutput[9] = m_pShiftToDepth[(shift[9])]; + pnOutput[10] = m_pShiftToDepth[(shift[10])]; + pnOutput[11] = m_pShiftToDepth[(shift[11])]; + pnOutput[12] = m_pShiftToDepth[(shift[12])]; + pnOutput[13] = m_pShiftToDepth[(shift[13])]; + pnOutput[14] = m_pShiftToDepth[(shift[14])]; + pnOutput[15] = m_pShiftToDepth[(shift[15])]; +#else + // input: 0, 1,2 (X8) + // -,---,- + // bits: 8,4,4,8 (X8) + // ---,--- + // output: 0, 1 (X8) + + // Split 24 bytes into 3 vectors (64 bit each) + inD3 = vld3_u8(pcInput); + + // rshft4D0 contains 4 MSB of second vector (placed at offset 0) + rshft4D = vshr_n_u8(inD3.val[1], 4); + // lshft4D0 contains 4 LSB of second vector (placed at offset 4) + lshft4D = vshl_n_u8(inD3.val[1], 4); + + // Expand 64 bit vectors to 128 bit (8 values of 16 bits) + shiftQ2.val[0] = vmovl_u8(inD3.val[0]); + shiftQ2.val[1] = vmovl_u8(inD3.val[2]); + rshft4Q = vmovl_u8(rshft4D); + lshft4Q = vmovl_u8(lshft4D); + + // Even indexed shift = 8 bits from first vector + 4 MSB bits of second vector + shiftQ2.val[0] = vshlq_n_u16(shiftQ2.val[0], 4); + shiftQ2.val[0] = vorrq_u16(shiftQ2.val[0], rshft4Q); + + // Odd indexed shift = 4 LSB bits of second vector + 8 bits from third vector + lshft4Q = vshlq_n_u16(lshft4Q, 4); + shiftQ2.val[1] = vorrq_u16(shiftQ2.val[1], lshft4Q); + + // Interleave shift values to a single vector + vst2q_u16(shift, shiftQ2); + + depth[0] = m_pShiftToDepth[(shift[0])]; + depth[1] = m_pShiftToDepth[(shift[1])]; + + depth[2] = m_pShiftToDepth[(shift[2])]; + depth[3] = m_pShiftToDepth[(shift[3])]; + + depth[4] = m_pShiftToDepth[(shift[4])]; + depth[5] = m_pShiftToDepth[(shift[5])]; + + depth[6] = m_pShiftToDepth[(shift[6])]; + depth[7] = m_pShiftToDepth[(shift[7])]; + + // Load + depthQ = vld1q_u16(depth); + //Store + vst1q_u16(pnOutput, depthQ); + + depth[8] = m_pShiftToDepth[(shift[8])]; + depth[9] = m_pShiftToDepth[(shift[9])]; + + depth[10] = m_pShiftToDepth[(shift[10])]; + depth[11] = m_pShiftToDepth[(shift[11])]; + + depth[12] = m_pShiftToDepth[(shift[12])]; + depth[13] = m_pShiftToDepth[(shift[13])]; + + depth[14] = m_pShiftToDepth[(shift[14])]; + depth[15] = m_pShiftToDepth[(shift[15])]; + + // Load + depthQ = vld1q_u16(depth + 8); + // Store + vst1q_u16(pnOutput + 8, depthQ); +#endif + pcInput += XN_INPUT_ELEMENT_SIZE; + pnOutput += 16; + } + + *pnActualRead = (XnUInt32)(pcInput - pOrigInput); // total bytes + *pnActualWritten = (XnUInt32)((XnUInt8*)pnOutput - pDest); + + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.h new file mode 100644 index 0000000..7ba174e --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink12BitS2DParser.h @@ -0,0 +1,39 @@ +#ifndef __XNLINK12BITS2DPARSER_H__ +#define __XNLINK12BITS2DPARSER_H__ + +#include "XnLinkMsgParser.h" +#include "XnShiftToDepth.h" + +/* The size of an input element in the stream. */ +#define XN_INPUT_ELEMENT_SIZE 24 +/* The size of an output element in the stream. */ +#define XN_OUTPUT_ELEMENT_SIZE 32 + +namespace xn +{ + +class Link12BitS2DParser : public LinkMsgParser +{ +public: + Link12BitS2DParser(const XnShiftToDepthTables& shiftToDepthTables); + virtual ~Link12BitS2DParser(); + +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + +private: + XnStatus Unpack12to16(const XnUInt8* pcInput,XnUInt8* pDest, const XnUInt32 nInputSize, XnUInt32* pnActualRead, XnUInt32* pnActualWritten); + XnUInt32 ProcessFramePacketChunk(const XnUInt8* pData,XnUInt8* pDest, XnUInt32 nDataSize); + + const OniDepthPixel* m_pShiftToDepth; + XnUInt32 m_ContinuousBufferSize; + XnUInt8 m_ContinuousBuffer[XN_INPUT_ELEMENT_SIZE]; +}; + +} + +#endif // __XNLINK12BITS2DPARSER_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.cpp new file mode 100644 index 0000000..1a39ca5 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.cpp @@ -0,0 +1,196 @@ +#include "XnLink16zParser.h" +#include "XnShiftToDepth.h" + +#define MAX_SMALL_DIFF_NIBBLE 0x0C +#define SMALL_DIFF_OFFSET 6 +#define BIG_DIFF_OFFSET 64 + +namespace xn +{ + +template +Link16zParser::Link16zParser(const XnShiftToDepthTables& shiftToDepthTables) : + m_pShiftToDepth(shiftToDepthTables.pShiftToDepthTable), + m_nShift(0), + m_nState(STATE_OPCODE), + m_nBigDiff(0), + m_nMaxShift((XnUInt16)(shiftToDepthTables.nShiftsCount - 1)) +{ +} + +template +Link16zParser::~Link16zParser() +{ + +} + +template<> +inline OniDepthPixel Link16zParser::TranslatePixel(XnUInt32 nShift) +{ + return m_pShiftToDepth[nShift]; +} + +template<> +inline OniDepthPixel Link16zParser::TranslatePixel(XnUInt32 nShift) +{ + return static_cast(nShift); +} + + +template +XnStatus Link16zParser::ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + OniDepthPixel*& pDstPixel = reinterpret_cast(pDst); //Reference to pointer - moving pDstPixel affects pDst also. + const OniDepthPixel* pDstPixelEnd = reinterpret_cast(pDstEnd); + XnUInt32 nRLERepeats = 0; + XnBool bReadHigh = TRUE; //TRUE when reading high part of byte, FALSE when reading low part + XnUInt32 nNibble = 0; + + if ((fragmentation & XN_LINK_FRAG_BEGIN) != 0) + { + //We purposely give m_nShift a value that will mess up diff calculations if it's in the beginning by mistake + m_nShift = (m_nMaxShift + BIG_DIFF_OFFSET + 1); + m_nBigDiff = 0; + m_nState = STATE_OPCODE; + bReadHigh = TRUE; + } + + //////////////////////////////////////////// + while ((pSrc < pSrcEnd) && (pDstPixel < pDstPixelEnd)) + { + //Read next nibble + if (bReadHigh) + { + //Read high nibble of this byte + nNibble = (*pSrc >> 4); + bReadHigh = FALSE; + } + else + { + //Read low nibble of this byte and skip to next byte + nNibble = (*pSrc & 0x0F); + pSrc++; + bReadHigh = TRUE; + } + + switch (m_nState) + { + case STATE_OPCODE: + if (nNibble <= MAX_SMALL_DIFF_NIBBLE) + { + //Read value is small diff - calculate shift and write it. + m_nShift += nNibble - SMALL_DIFF_OFFSET; + if (m_nShift > m_nMaxShift) + { +// XN_ASSERT(FALSE); + m_nState = STATE_BAD_FRAME; + return XN_STATUS_LINK_BAD_PACKET_FORMAT; + } + *pDstPixel++ = TranslatePixel(m_nShift); + //Stay in STATE_OPCODE + } + else + { + m_nState = State(nNibble); //Next state is determined by opcode nibble + } + break; + case STATE_RLE: + if (m_nShift > m_nMaxShift) + { +// XN_ASSERT(FALSE); + m_nState = STATE_BAD_FRAME; + return XN_STATUS_LINK_BAD_PACKET_FORMAT; + } + + //Write as many values as needed or just fill remaining space. + nRLERepeats = XN_MIN(nNibble + 1, XnUInt32(pDstPixelEnd - pDstPixel)); + while (nRLERepeats > 0) + { + *pDstPixel++ = TranslatePixel(m_nShift); + nRLERepeats--; + } + m_nState = STATE_OPCODE; + break; + case STATE_FULL_ENC1: + if (nNibble & 0x08) + { + //Take 3 lower bits from nibble, make room for 4 bits more + m_nBigDiff = (nNibble & 0x07) << 4; + m_nState = STATE_FULL_ENC_BIG_DIFF; + } + else + { + //Take 3 lower bits from nibble, make room for 12 bits more + //m_nShift = (nNibble & 0x07) << 12; + //There's no point doing this since max shift is 12 bits + m_nState = State(m_nState + 1); //To STATE_FULL_ENC2 + } + break; + case STATE_FULL_ENC2: + //Take 4 bits from nibble, make room for 8 more + //m_nShift |= (nNibble << 8); + //There's no point saving anything from the previous shift value since max shift is 12 bits + m_nShift = (nNibble << 8); + m_nState = State(m_nState + 1); //To STATE_FULL_ENC3 + break; + case STATE_FULL_ENC3: + //Take 4 bits from nibble, make room for 4 more + m_nShift |= (nNibble << 4); + m_nState = State(m_nState + 1); //To STATE_FULL_ENC4 + break; + case STATE_FULL_ENC4: + //Take 4 bits from nibble, write value to destination + m_nShift |= nNibble; + if (m_nShift > m_nMaxShift) + { + XN_ASSERT(FALSE); + return XN_STATUS_LINK_RESP_CORRUPT_PACKET; + } + *pDstPixel++ = TranslatePixel(m_nShift); + m_nState = STATE_OPCODE; + break; + case STATE_FULL_ENC_BIG_DIFF: + //Take 4 bits from nibble, write value with difference to destination + m_nBigDiff |= nNibble; + m_nShift += (m_nBigDiff - BIG_DIFF_OFFSET); + if (m_nShift > m_nMaxShift) + { +// XN_ASSERT(FALSE); + m_nState = STATE_BAD_FRAME; + return XN_STATUS_LINK_BAD_PACKET_FORMAT; + } + *pDstPixel++ = TranslatePixel(m_nShift); + m_nState = STATE_OPCODE; + break; + case STATE_BAD_FRAME: + //We stay in this state until the next frame beginning arrives + return XN_STATUS_LINK_BAD_PACKET_FORMAT; + default: + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } //switch + } //while + //////////////////////////////////////////// + +/* if (pSrc < pSrcEnd) + { + //We had more bytes in input but not enough space in output to write them + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } +*/ + + return XN_STATUS_OK; +} + +//--------------------------------------------------------------------------- +// Explicit instantiations +//--------------------------------------------------------------------------- +template class Link16zParser; +template class Link16zParser; + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.h new file mode 100644 index 0000000..45725ee --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink16zParser.h @@ -0,0 +1,44 @@ +#include "XnLinkMsgParser.h" + +struct XnShiftToDepthTables; + +namespace xn +{ + +template +class Link16zParser : public LinkMsgParser +{ +public: + Link16zParser(const XnShiftToDepthTables& shiftToDepthTables); + virtual ~Link16zParser(); + +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); +private: + inline OniDepthPixel TranslatePixel(XnUInt32 nShift); + + const OniDepthPixel* m_pShiftToDepth; + XnUInt32 m_nShift; + + enum State + { + STATE_OPCODE = 0x0D, + STATE_RLE = 0x0E, + STATE_FULL_ENC1 = 0x0F, + STATE_FULL_ENC2 = 0x10, + STATE_FULL_ENC3 = 0x11, + STATE_FULL_ENC4 = 0x12, + STATE_FULL_ENC_BIG_DIFF = 0x13, + STATE_BAD_FRAME = 0xFF, + }; + + State m_nState; + XnUInt32 m_nBigDiff; + XnUInt16 m_nMaxShift; +}; + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.cpp new file mode 100644 index 0000000..b7d3445 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.cpp @@ -0,0 +1,232 @@ +#include + +#include "XnLink24zYuv422Parser.h" +#include "XnLinkYuvToRgb.h" + +namespace xn +{ + +Link24zYuv422Parser::Link24zYuv422Parser(XnUInt32 xRes, XnUInt32 yRes, XnBool transformToRGB) : + m_dataFromPrevPacket(NULL), + m_dataFromPrevPacketBytes(0), + m_lineWidthBytes(xRes * LinkYuvToRgb::YUV_422_BYTES_PER_PIXEL), // 4 bytes for every 2 pixels + m_rgbFrameSize(xRes * yRes * LinkYuvToRgb::RGB_888_BYTES_PER_PIXEL), + m_expectedFrameSize(xRes * yRes * LinkYuvToRgb::YUV_422_BYTES_PER_PIXEL), + m_transformToRGB(transformToRGB), + m_tempYuvImage(NULL), + m_tempYuvImageBytes(0) +{ +} + +Link24zYuv422Parser::~Link24zYuv422Parser() +{ + xnOSFree(m_dataFromPrevPacket); + xnOSFree(m_tempYuvImage); +} + +XnStatus Link24zYuv422Parser::Init() +{ + m_dataFromPrevPacket = (XnUInt8*)xnOSMallocAligned(m_lineWidthBytes, XN_DEFAULT_MEM_ALIGN); + XN_VALIDATE_ALLOC_PTR(m_dataFromPrevPacket); + + if (m_transformToRGB) + { + m_tempYuvImage = (XnUInt8*)xnOSMallocAligned(m_rgbFrameSize, XN_DEFAULT_MEM_ALIGN); + XN_VALIDATE_ALLOC_PTR(m_tempYuvImage); + } + + return XN_STATUS_OK; +} + +XnStatus Link24zYuv422Parser::ParsePacketImpl(XnLinkFragmentation fragmentation, const XnUInt8* pSrc, const XnUInt8* pSrcEnd, XnUInt8*& pDst, const XnUInt8* pDstEnd) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if ((fragmentation | XN_LINK_FRAG_BEGIN) != 0) + { + m_dataFromPrevPacketBytes = 0; + m_tempYuvImageBytes = 0; + } + + const XnUInt8* pInput = pSrc; + XnSizeT inputSize = pSrcEnd - pSrc; + + // if there's data left from previous packet, append new data to it + if (m_dataFromPrevPacketBytes > 0) + { + if (m_dataFromPrevPacketBytes + inputSize > m_expectedFrameSize) + { + XN_ASSERT(FALSE); + m_dataFromPrevPacketBytes = 0; + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(m_dataFromPrevPacket, pSrc, inputSize); + pInput = m_dataFromPrevPacket; + inputSize = m_dataFromPrevPacketBytes + inputSize; + } + + XnUInt8* pOutput = pDst; + XnSizeT outputSize = pDstEnd - pDst; + + if (m_transformToRGB) + { + pOutput = m_tempYuvImage + m_tempYuvImageBytes; + outputSize = m_rgbFrameSize - m_tempYuvImageBytes; + } + + XnSizeT actualRead; + nRetVal = Uncompress24z(pInput, inputSize, pOutput, &outputSize, m_lineWidthBytes, &actualRead, (fragmentation | XN_LINK_FRAG_END) == XN_LINK_FRAG_END); + XN_IS_STATUS_OK(nRetVal); + + pDst += outputSize; + + // if we have bytes left, keep them for next packet + if (actualRead < inputSize) + { + m_dataFromPrevPacketBytes = inputSize - actualRead; + xnOSMemMove(m_dataFromPrevPacket, pInput + actualRead, m_dataFromPrevPacketBytes); + } + + if ((fragmentation | XN_LINK_FRAG_END) != 0) + { + outputSize = pDstEnd - pDst; + LinkYuvToRgb::Yuv422ToRgb888(m_tempYuvImage, m_tempYuvImageBytes, pDst, outputSize); + pDst += outputSize; + } + + return (XN_STATUS_OK); +} + +XnStatus Link24zYuv422Parser::Uncompress24z(const XnUInt8* pInput, XnSizeT nInputSize, + XnUInt8* pOutput, XnSizeT* pnOutputSize, XnUInt32 nLineSize, + XnSizeT* pnActualRead, XnBool bLastPart) +{ + // Input is made of 4-bit elements. + const XnUInt8* pInputOrig = pInput; + const XnUInt8* pInputEnd = pInput + nInputSize; + XnUInt8* pOrigOutput = pOutput; + XnUInt8* pOutputEnd = pOutput + (*pnOutputSize); + XnUInt8 nLastFullValue[4] = {0}; + + // NOTE: we use variables of type uint32 instead of uint8 as an optimization (better CPU usage) + XnUInt32 nTempValue = 0; + XnUInt32 cInput = 0; + XnBool bReadByte = TRUE; + + const XnUInt8* pInputLastPossibleStop = pInputOrig; + XnUInt8* pOutputLastPossibleStop = pOrigOutput; + + *pnActualRead = 0; + *pnOutputSize = 0; + + XnUInt32 nChannel = 0; + XnUInt32 nCurLineSize = 0; + + while (pInput < pInputEnd) + { + cInput = *pInput; + + if (bReadByte) + { + bReadByte = FALSE; + + if (cInput < 0xd0) // 0x0 to 0xc are diffs + { + // take high_element only + // diffs are between -6 and 6 (0x0 to 0xc) + nLastFullValue[nChannel] += XnInt8((cInput >> 4) - 6); + } + else if (cInput < 0xe0) // 0xd is dummy + { + // Do nothing + continue; + } + else // 0xe is not used, so this must be 0xf - full + { + // take two more elements + nTempValue = (cInput & 0x0f) << 4; + + if (++pInput == pInputEnd) + break; + + nTempValue += (*pInput >> 4); + nLastFullValue[nChannel] = (XnUInt8)nTempValue; + } + } + else + { + // take low-element + cInput &= 0x0f; + bReadByte = TRUE; + pInput++; + + if (cInput < 0xd) // 0x0 to 0xc are diffs + { + // diffs are between -6 and 6 (0x0 to 0xc) + nLastFullValue[nChannel] += (XnInt8)(cInput - 6); + } + else if (cInput < 0xe) // 0xd is dummy + { + // Do nothing + continue; + } + else // 0xe is not in use, so this must be 0xf - full + { + if (pInput == pInputEnd) + break; + + // take two more elements + nLastFullValue[nChannel] = *pInput; + pInput++; + } + } + + // write output + if (pOutput > pOutputEnd) + { + return (XN_STATUS_OUTPUT_BUFFER_OVERFLOW); + } + + *pOutput = nLastFullValue[nChannel]; + pOutput++; + + nChannel++; + switch (nChannel) + { + case 2: + nLastFullValue[3] = nLastFullValue[1]; + break; + case 4: + nLastFullValue[1] = nLastFullValue[3]; + nChannel = 0; + break; + } + + nCurLineSize++; + if (nCurLineSize == nLineSize) + { + pInputLastPossibleStop = pInput; + pOutputLastPossibleStop = pOutput; + + nLastFullValue[0] = nLastFullValue[1] = nLastFullValue[2] = nLastFullValue[3] = 0; + nCurLineSize = 0; + } + } + + if (bLastPart == TRUE) + { + *pnOutputSize = (pOutput - pOrigOutput); + *pnActualRead += (pInput - pInputOrig); + } + else if ((pOutputLastPossibleStop != pOrigOutput) && (pInputLastPossibleStop != pInputOrig)) + { + *pnOutputSize = (pOutputLastPossibleStop - pOrigOutput); + *pnActualRead +=(pInputLastPossibleStop - pInputOrig); + } + + // All is good... + return (XN_STATUS_OK); +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.h new file mode 100644 index 0000000..eb9d74e --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink24zYuv422Parser.h @@ -0,0 +1,44 @@ +#ifndef _XN_LINK_24Z_YUV422_PARSER_H_ +#define _XN_LINK_24Z_YUV422_PARSER_H_ + +#include "XnLinkMsgParser.h" + +namespace xn +{ + +class Link24zYuv422Parser : public LinkMsgParser +{ +public: + Link24zYuv422Parser(XnUInt32 xRes, XnUInt32 yRes, XnBool transformToRGB); + virtual ~Link24zYuv422Parser(); + + virtual XnStatus Init(); + +protected: + virtual XnStatus ParsePacketImpl( + XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + +private: + XnStatus Uncompress24z( + const XnUInt8* pInput, XnSizeT nInputSize, + XnUInt8* pOutput, XnSizeT* pnOutputSize, XnUInt32 nLineSize, + XnSizeT* pnActualRead, XnBool bLastPart); + + XnUInt8* m_dataFromPrevPacket; + XnSizeT m_dataFromPrevPacketBytes; + XnUInt32 m_lineWidthBytes; + XnUInt32 m_rgbFrameSize; + XnUInt32 m_expectedFrameSize; + XnBool m_transformToRGB; + XnUInt8* m_tempYuvImage; // hold Yuv Image, when transform is required + XnUInt32 m_tempYuvImageBytes; +}; + +} + + +#endif //_XN_LINK_24Z_YUV422_PARSER_H_ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.cpp new file mode 100644 index 0000000..ff544a6 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.cpp @@ -0,0 +1,88 @@ +#include "XnLink6BitParser.h" +#include "XnLinkProtoUtils.h" +#include + +namespace xn +{ + +Link6BitParser::Link6BitParser() : + m_nState(0), + m_nShift(0) +{ +} + +Link6BitParser::~Link6BitParser() +{ +} + +XnStatus Link6BitParser::ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + OniDepthPixel*& pDstPixel = reinterpret_cast(pDst); + const OniDepthPixel* pDstPixelEnd = reinterpret_cast(pDstEnd); + XnSizeT nPacketBits = 0; + XnSizeT nPacketDstWords = 0; + + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + + if ((fragmentation & XN_LINK_FRAG_BEGIN) != 0) + { + //Reset state for new frame + m_nState = 0; + } + + nPacketBits = (nPacketDataSize * 8); + nPacketDstWords = (nPacketBits / 6); + if ((nPacketBits % 6) != 0) + { + nPacketDstWords++; + } + + if ((pDstPixel + nPacketDstWords) > pDstPixelEnd) //Do we have enough room for this packet? + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + while (pSrc < pSrcEnd) + { + XN_ASSERT(pDstPixel < pDstPixelEnd); + if (pSrc + 1 == pSrcEnd && (m_nState != 0 || m_nState != 3)) + break; + switch (m_nState) + { + case 0: + *pDstPixel++ = (*pSrc & 0x3f); //6 first bits + m_nState++; + break; + case 1: + *pDstPixel++ = + (*pSrc >> 6) | //last 2 bits from prev frame + ((pSrc[1] & 0xf) << 2); //4 first bits put at the end of frame + pSrc++; + m_nState++; + break; + case 2: + *pDstPixel++ = + (*pSrc >> 4) | //last 4 bits, for start new frame + ((pSrc[1] & 0x3f) << 2); //first 2 bits, put at the end of frame + pSrc++; + m_nState++; + break; + case 3: + *pDstPixel++ = (*pSrc >> 6); //last 6 bits it is the frame + pSrc++; + m_nState = 0; + break; + default: + XN_ASSERT(FALSE); + }//switch + }// while + + return XN_STATUS_OK; +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.h new file mode 100644 index 0000000..c48ba2b --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLink6BitParser.h @@ -0,0 +1,29 @@ +#ifndef _XNLINK6BITPARSER_H_ +#define _XNLINK6BITPARSER_H_ + +#include "XnLinkMsgParser.h" + +namespace xn +{ + +class Link6BitParser : public LinkMsgParser +{ +public: + Link6BitParser(); + virtual ~Link6BitParser(); + +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + +private: + XnUInt32 m_nState; + XnUInt16 m_nShift; +}; + +} + +#endif // _XNLINK6BITPARSER_H_ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.cpp new file mode 100644 index 0000000..c4c14b2 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.cpp @@ -0,0 +1,290 @@ +#include "XnLinkContInputStream.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkProtoLibDefs.h" +#include "XnCyclicBuffer.h" +#include "XnLinkControlEndpoint.h" +#include +#include + +#define XN_MASK_INPUT_STREAM "xnInputStream" + +namespace xn +{ + + +const XnUInt32 LinkContInputStream::CONT_STREAM_PREDEFINED_BUFFER_SIZE = 0x40000; + +LinkContInputStream::LinkContInputStream() +{ + m_bInitialized = FALSE; + m_bStreaming = FALSE; + m_bNewDataAvailable = FALSE; + m_hCriticalSection = NULL; + m_nUserBufferMaxSize = 0; + m_pUserBuffer = NULL; + m_nUserBufferCurrentSize = 0; + xnOSCreateCriticalSection(&m_hCriticalSection); + m_pDumpFile = NULL; + xnOSMemSet(m_strDumpName, 0, sizeof(m_strDumpName)); +} + +LinkContInputStream::~LinkContInputStream() +{ + Shutdown(); + xnOSCloseCriticalSection(&m_hCriticalSection); +} + +XnStatus LinkContInputStream::Init(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection) +{ + XnStatus nRetVal = XN_STATUS_OK; + if (m_hCriticalSection == NULL) + { + xnLogError(XN_MASK_INPUT_STREAM, "Cannot initialize - critical section was not created successfully"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + xnl::AutoCSLocker csLock(m_hCriticalSection); + if (m_bInitialized) + { + //We shutdown first so we can re-initialize. + Shutdown(); + } + + nRetVal = LinkInputStream::Init(pLinkControlEndpoint, streamType, nStreamID, pConnection); + XN_IS_STATUS_OK_LOG_ERROR("Init base input stream", nRetVal); + + m_nStreamID = nStreamID; + m_nUserBufferMaxSize = CONT_STREAM_PREDEFINED_BUFFER_SIZE; + m_nUserBufferCurrentSize = m_nWorkingBufferCurrentSize = 0; + //Allocate buffers + m_pUserBuffer = reinterpret_cast(xnOSCallocAligned(1, m_nUserBufferMaxSize, XN_DEFAULT_MEM_ALIGN)); + if (m_pUserBuffer == NULL) + { + Shutdown(); + xnLogError(XN_MASK_INPUT_STREAM, "Failed to allocate buffer of size %u", m_nUserBufferMaxSize); + XN_ASSERT(FALSE); + return XN_STATUS_ALLOC_FAILED; + } + m_pWorkingBuffer = reinterpret_cast(xnOSCallocAligned(1, CONT_STREAM_PREDEFINED_BUFFER_SIZE, XN_DEFAULT_MEM_ALIGN)); + if (m_pWorkingBuffer == NULL) + { + Shutdown(); + xnLogError(XN_MASK_INPUT_STREAM, "Failed to allocate buffer of size %u", m_nUserBufferMaxSize); + XN_ASSERT(FALSE); + return XN_STATUS_ALLOC_FAILED; + } + + nRetVal = xnLinkGetStreamDumpName(m_nStreamID, m_strDumpName, sizeof(m_strDumpName)); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_INPUT_STREAM, "Failed to get stream dump name: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } + + m_bInitialized = TRUE; + return XN_STATUS_OK; +} + +XnBool LinkContInputStream::IsInitialized() const +{ + return m_bInitialized; +} + +void LinkContInputStream::Shutdown() +{ + if (!m_bInitialized) + return; + + xnOSEnterCriticalSection(&m_hCriticalSection); + + XN_ALIGNED_FREE_AND_NULL(m_pUserBuffer); + XN_ALIGNED_FREE_AND_NULL(m_pWorkingBuffer); + + m_bInitialized = FALSE; + m_bNewDataAvailable = FALSE; + LinkInputStream::Shutdown(); + + xnOSLeaveCriticalSection(&m_hCriticalSection); +} + +XnStatus LinkContInputStream::HandlePacket(const LinkPacketHeader& header, const XnUInt8* pData, XnBool& bPacketLoss) +{ + XnStatus nRetVal = XN_STATUS_OK; + xnl::AutoCSLocker csLock(m_hCriticalSection); + if (!m_bInitialized) + { + return XN_STATUS_NOT_INIT; + } + + // TODO: handle packet loss! + bPacketLoss = FALSE; + + if(m_streamType == XN_LINK_STREAM_TYPE_LOG) + { + // begin parsing frame + nRetVal = m_logParser.BeginParsing(m_pWorkingBuffer, CONT_STREAM_PREDEFINED_BUFFER_SIZE); + XN_IS_STATUS_OK_LOG_ERROR("Begin parsing link log msg", nRetVal); + + nRetVal = m_logParser.ParsePacket(header, pData); + + if (nRetVal != XN_STATUS_OK) + XN_IS_STATUS_OK_LOG_ERROR("Parse data from stream", nRetVal); + } + + //Write new data to dump (if it's on) + xnDumpFileWriteBuffer(m_pDumpFile, + reinterpret_cast(m_logParser.GetParsedData()), + m_logParser.GetParsedSize()); + + if (header.GetFragmentationFlags() & XN_LINK_FRAG_END) + { + //Notify that we have new data available + m_bNewDataAvailable = TRUE; + nRetVal = m_newDataAvailableEvent.Raise(); + XN_IS_STATUS_OK_LOG_ERROR("Raise new data available event", nRetVal); + } + + return XN_STATUS_OK; +} + +const void* LinkContInputStream::GetData() const +{ + return m_pUserBuffer; +} + +XnUInt32 LinkContInputStream::GetDataSize() const +{ + return m_nUserBufferCurrentSize; +} + +const void* LinkContInputStream::GetNextData() const +{ + static const XnUInt64 nDummy = 0; + //TODO: Implement this properly for timestamps... + XN_ASSERT(FALSE); + return &nDummy; +} + +XnUInt32 LinkContInputStream::GetNextDataSize() const +{ + return 0; +} + +XnBool LinkContInputStream::IsNewDataAvailable() const +{ + xnOSEnterCriticalSection(&m_hCriticalSection); + if (!m_bInitialized) + { + return FALSE; + } + XnBool bNewDataAvailable = m_bNewDataAvailable; + xnOSLeaveCriticalSection(&m_hCriticalSection); + + return bNewDataAvailable; +} + +XnStatus LinkContInputStream::StartImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + if (m_bStreaming) + { + return XN_STATUS_OK; + } + + m_pDumpFile = xnDumpFileOpen(m_strDumpName, "%s", m_strDumpName); + + //We only need log buffer output if dumping is on + m_logParser.GenerateOutputBuffer(m_pDumpFile != NULL); + + nRetVal = m_pConnection->Connect(); + XN_IS_STATUS_OK_LOG_ERROR("Connect stream's input connection", nRetVal); + nRetVal = m_pLinkControlEndpoint->StartStreaming(m_nStreamID); + XN_IS_STATUS_OK_LOG_ERROR("Start streaming", nRetVal); + m_bStreaming = TRUE; + + return XN_STATUS_OK; +} + +XnStatus LinkContInputStream::StopImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + if (!m_bStreaming) + { + return XN_STATUS_OK; + } + + nRetVal = m_pLinkControlEndpoint->StopStreaming(m_nStreamID); + XN_IS_STATUS_OK_LOG_ERROR("Stop streaming", nRetVal); + m_pConnection->Disconnect(); + m_bStreaming = FALSE; + xnDumpFileClose(m_pDumpFile); + + return XN_STATUS_OK; +} + +XnBool LinkContInputStream::IsStreaming() const +{ + return m_bStreaming; +} + +XnStatus LinkContInputStream::UpdateData() +{ + xnl::AutoCSLocker csLock(m_hCriticalSection); + if (!m_bInitialized) + { + xnLogError(XN_MASK_INPUT_STREAM, "Attempted to update data from stream %u which is not initialized", m_nStreamID); + XN_ASSERT(FALSE); + return XN_STATUS_NOT_INIT; + } + + if (m_bNewDataAvailable) + { + //Copy working buffer to user buffer + xnOSMemCopy(m_pUserBuffer, m_pWorkingBuffer, m_nUserBufferMaxSize); + m_nUserBufferCurrentSize = m_nWorkingBufferCurrentSize; + m_bNewDataAvailable = FALSE; + } + + return XN_STATUS_OK; +} + +XnStatus LinkContInputStream::RegisterToNewDataAvailable(NewDataAvailableHandler pHandler, void* pCookie, XnCallbackHandle& hCallback) +{ + return m_newDataAvailableEvent.Register(pHandler, pCookie, hCallback); +} + +void LinkContInputStream::UnregisterFromNewDataAvailable(XnCallbackHandle hCallback) +{ + m_newDataAvailableEvent.Unregister(hCallback); +} + +void LinkContInputStream::SetDumpName(const XnChar* strDumpName) +{ + XnStatus nRetVal = XN_STATUS_OK; + (void)nRetVal; + nRetVal = xnOSStrCopy(m_strDumpName, strDumpName, sizeof(m_strDumpName)); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_INPUT_STREAM, "Failed to set dump name: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } +} + +void LinkContInputStream::SetDumpOn(XnBool bDumpOn) +{ + XnStatus nRetVal = XN_STATUS_OK; + (void)nRetVal; + + nRetVal = xnDumpSetMaskState(m_strDumpName, bDumpOn); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_INPUT_STREAM, "Failed to set dump state: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } +} +} + diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.h new file mode 100644 index 0000000..a5ff7df --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkContInputStream.h @@ -0,0 +1,79 @@ +#ifndef __XNLINKCONTINPUTSTREAM_H__ +#define __XNLINKCONTINPUTSTREAM_H__ + +#include "XnLinkMsgParser.h" +#include "XnLinkInputStream.h" +#include +#include +#include + +#include "XnLinkLogParser.h" + +typedef XnUInt32 XnStreamFormat; + +namespace xn +{ + +class LinkContInputStream : public LinkInputStream +{ +public: + LinkContInputStream(); + virtual ~LinkContInputStream(); + virtual XnStatus Init(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection); + //LinkInputStream methods + virtual XnBool IsInitialized() const; + virtual void Shutdown(); + virtual XnStatus HandlePacket(const LinkPacketHeader& header, const XnUInt8* pData, XnBool& bPacketLoss); + virtual const void* GetData() const; + virtual XnUInt32 GetDataSize() const; + virtual XnUInt64 GetTimestamp() const { return 0; } + virtual const void* GetNextData() const; + virtual XnUInt32 GetNextDataSize() const; + virtual XnUInt64 GetNextTimestamp() const { return 0; } + virtual XnBool IsNewDataAvailable() const; + virtual XnStatus UpdateData(); + + virtual XnBool IsStreaming() const; + + virtual XnStreamFragLevel GetStreamFragLevel() const { return XN_LINK_STREAM_FRAG_LEVEL_CONTINUOUS; } + + typedef void (XN_CALLBACK_TYPE* NewDataAvailableHandler)(void* pCookie); + virtual XnStatus RegisterToNewDataAvailable(NewDataAvailableHandler pHandler, void* pCookie, XnCallbackHandle& hCallback); + virtual void UnregisterFromNewDataAvailable(XnCallbackHandle hCallback); + + //Other methods + virtual void SetDumpName(const XnChar* strDumpName); + virtual void SetDumpOn(XnBool bDumpOn); + +protected: + virtual XnStatus StartImpl(); + virtual XnStatus StopImpl(); + +private: + LinkLogParser m_logParser; + + static const XnUInt32 CONT_STREAM_PREDEFINED_BUFFER_SIZE; + mutable XN_CRITICAL_SECTION_HANDLE m_hCriticalSection; //Protects buffers info + XnBool m_bNewDataAvailable; + XnBool m_bInitialized; + XnBool m_bStreaming; + + XnUInt32 m_nUserBufferMaxSize; + XnUInt32 m_nUserBufferCurrentSize; + XnUInt8* m_pUserBuffer; + + XnUInt32 m_nWorkingBufferCurrentSize; + XnUInt8* m_pWorkingBuffer; + + XnChar m_strDumpName[XN_FILE_MAX_PATH]; + XnDumpFile* m_pDumpFile; + + xnl::EventNoArgs m_newDataAvailableEvent; +}; + +} + +#endif // __XNLINKCONTINPUTSTREAM_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.cpp new file mode 100644 index 0000000..6d61c54 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.cpp @@ -0,0 +1,1742 @@ +#include "XnLinkControlEndpoint.h" +#include "XnLinkMsgEncoder.h" +#include "XnLinkMsgParser.h" +#include "IConnectionFactory.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkStatusCodes.h" +#include "XnShiftToDepth.h" +#include "XnLinkProtoLibDefs.h" +#include +#include +#include + +#define MAX_PROP_SIZE 2048 + +namespace xn +{ + +const XnUInt16 LinkControlEndpoint::BASE_PACKET_ID = 1; //Packet IDs start from 1 +const XnUInt16 LinkControlEndpoint::MAX_RESPONSE_NUM_PACKETS = 8; +const XnChar LinkControlEndpoint::MUTEX_NAME[] = "XnLinkControlEPMutex"; +const XnUInt32 LinkControlEndpoint::MUTEX_TIMEOUT = 20000; + + +LinkControlEndpoint::LinkControlEndpoint() +{ + m_pIncomingRawPacket = NULL; + m_nMaxOutMsgSize = 0; + m_pConnection = NULL; + m_pIncomingResponse = NULL; + m_nMaxResponseSize = 0; + m_bInitialized = FALSE; + m_bConnected = FALSE; + m_nPacketID = BASE_PACKET_ID; + m_nMaxPacketSize = 0; + m_hMutex = NULL; +} + +LinkControlEndpoint::~LinkControlEndpoint() +{ + Shutdown(); +} + +XnStatus LinkControlEndpoint::Init(XnUInt32 nMaxOutMsgSize, IConnectionFactory* pConnectionFactory) +{ + XN_VALIDATE_INPUT_PTR(pConnectionFactory); + XnStatus nRetVal = XN_STATUS_OK; + if (!m_bInitialized) + { + m_nMaxOutMsgSize = nMaxOutMsgSize; + nRetVal = pConnectionFactory->GetControlConnection(m_pConnection); + XN_IS_STATUS_OK_LOG_ERROR("Create control connection", nRetVal); + //TODO: Once we have service discovery, ask the device what's the max packet size. + //In any way, we DON'T want to ask m_pConnection what its packet size is, cuz that's the low level packet size. + +#if XN_PLATFORM == XN_PLATFORM_ANDROID_ARM + nRetVal = xnOSCreateMutex(&m_hMutex); + XN_IS_STATUS_OK(nRetVal); +#else + nRetVal = xnOSCreateNamedMutex(&m_hMutex, MUTEX_NAME); + XN_IS_STATUS_OK_LOG_ERROR("Create named mutext", nRetVal); +#endif + + /* Initialize supported msg types array with commands that must always be supported */ + nRetVal = m_supportedMsgTypes.SetMinSize(XN_LINK_INTERFACE_PROPS + 1); + XN_IS_STATUS_OK_LOG_ERROR("Add to supported msg types", nRetVal); + nRetVal = m_supportedMsgTypes[XN_LINK_INTERFACE_PROPS].Set(XN_LINK_MSG_GET_PROP & 0xFF, TRUE); + XN_IS_STATUS_OK_LOG_ERROR("Add to supported msg types", nRetVal); + + m_bInitialized = TRUE; + } + + return XN_STATUS_OK; +} + +void LinkControlEndpoint::Shutdown() +{ + if (m_pConnection != NULL) + { + Disconnect(); + m_pConnection = NULL; + } + + if (m_hMutex != NULL) + { + xnOSCloseMutex(&m_hMutex); + m_hMutex = NULL; + } + + m_bInitialized = FALSE; +} + +XnStatus LinkControlEndpoint::Connect() +{ + XnStatus nRetVal = XN_STATUS_OK; + if (!m_bInitialized) + { + XN_LOG_ERROR_RETURN(XN_STATUS_NOT_INIT, XN_MASK_LINK, "Not initialized"); + } + + if (!m_bConnected) + { + nRetVal = m_pConnection->Connect(); + XN_IS_STATUS_OK_LOG_ERROR("Connect control connection", nRetVal); + m_nPacketID = BASE_PACKET_ID; + + //First thing we must do - get logical max packet size - sending other commands depends on this. + nRetVal = GetLogicalMaxPacketSize(m_nMaxPacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Get logical max packet size", nRetVal); + + //Now that we have the device's logical max packet size we can initialize the msg encoder and parser + nRetVal = m_msgEncoder.Init(m_nMaxOutMsgSize, m_nMaxPacketSize); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_LINK, "LINK: Failed to init msg encoder: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + Disconnect(); + return nRetVal; + } + + nRetVal = m_responseMsgParser.Init(); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_LINK, "LINK: Failed to init msg parser: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + Disconnect(); + return nRetVal; + } + + m_pIncomingRawPacket = reinterpret_cast(xnOSMallocAligned(m_nMaxPacketSize, XN_DEFAULT_MEM_ALIGN)); + if (m_pIncomingRawPacket == NULL) + { + xnLogError(XN_MASK_LINK, "LINK: Failed to allocate incoming packet"); + XN_ASSERT(FALSE); + Disconnect(); + return XN_STATUS_ALLOC_FAILED; + } + m_nMaxResponseSize = MAX_RESPONSE_NUM_PACKETS * m_nMaxPacketSize; + m_pIncomingResponse = reinterpret_cast(xnOSMallocAligned(m_nMaxResponseSize, XN_DEFAULT_MEM_ALIGN)); + if (m_pIncomingResponse == NULL) + { + xnLogError(XN_MASK_LINK, "LINK: Failed to allocate incoming response"); + XN_ASSERT(FALSE); + Disconnect(); + return XN_STATUS_ALLOC_FAILED; + } + + //Now that all our encoding and parsing objects are ready we can get other properties + nRetVal = GetSupportedMsgTypes(m_supportedMsgTypes); + XN_IS_STATUS_OK_LOG_ERROR("Get supported msg types", nRetVal); + + m_bConnected = TRUE; + } + + return XN_STATUS_OK; +} + +void LinkControlEndpoint::Disconnect() +{ + //Shutdown everything we initialized in Connect(). + m_msgEncoder.Shutdown(); + m_responseMsgParser.Shutdown(); + XN_ALIGNED_FREE_AND_NULL(m_pIncomingRawPacket); + m_pIncomingRawPacket = NULL; + + XN_ALIGNED_FREE_AND_NULL(m_pIncomingResponse); + m_pIncomingResponse = NULL; + + //We don't disconnect the actual connection cuz it is cached and doesn't belong to us. + m_bConnected = FALSE; +} + +XnBool LinkControlEndpoint::IsConnected() const +{ + return m_bConnected; +} + +XnStatus LinkControlEndpoint::ExecuteCommand(XnUInt16 nMsgType, + XnUInt16 nStreamID, + const void* pCmdData, + XnUInt32 nCmdSize, + void* pResponseData, + XnUInt32& nResponseSize, + XnBool* pIsLast /*= NULL*/) +{ + XnStatus nRetVal = XN_STATUS_OK; + xnl::AutoMutexLocker mutexLocker(m_hMutex, MUTEX_TIMEOUT); + XN_IS_STATUS_OK_LOG_ERROR("Lock mutex", mutexLocker.GetStatus()); + + XnBool autoContinue = (pIsLast == NULL); + XnBool isLast; + + /*XN_LINK_FRAG_SINGLE in this case indicates a single 'block' of data, not necessarily a single + packet. */ + nRetVal = ExecuteImpl(nMsgType, nStreamID, pCmdData, nCmdSize, XN_LINK_FRAG_SINGLE, pResponseData, nResponseSize, autoContinue, isLast); + XN_IS_STATUS_OK_LOG_ERROR("Send Data", nRetVal); + + if (pIsLast != NULL) + { + *pIsLast = isLast; + } + + return XN_STATUS_OK; +} + +XnUInt16 LinkControlEndpoint::GetPacketID() const +{ + return m_nPacketID; +} + +XN_MUTEX_HANDLE LinkControlEndpoint::GetMutex() const +{ + return m_hMutex; +} + +XnBool LinkControlEndpoint::IsMsgTypeSupported(XnUInt16 nMsgType) +{ + XnUInt8 nMsgTypeHi = ((nMsgType >> 8) & 0xFF); + XnUInt8 nMsgTypeLo = (nMsgType & 0xFF); + return (nMsgTypeHi < m_supportedMsgTypes.GetSize()) && (m_supportedMsgTypes[nMsgTypeHi].IsSet(nMsgTypeLo)); +} + +XnStatus LinkControlEndpoint::GetFWVersion(XnLinkDetailedVersion& version) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting FW version..."); + + XnLinkDetailedVersion linkVersion; + XnUInt32 nPropSize = sizeof(linkVersion); + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_FW_VERSION, nPropSize, &linkVersion); + XN_IS_STATUS_OK_LOG_ERROR("Execute get version command", nRetVal); + + if (nPropSize != sizeof(linkVersion)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of FW version property: %u instead of %u", nPropSize, sizeof(linkVersion)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + xnLinkParseDetailedVersion(version, linkVersion); + + xnLogInfo(XN_MASK_LINK, "LINK: FW version is %u.%u.%u.%u-%s", version.m_nMajor, version.m_nMinor, version.m_nMaintenance, version.m_nBuild, version.m_strModifier); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetProtocolVersion(XnLeanVersion& version) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting protocol version..."); + + XnLinkLeanVersion linkVersion; + XnUInt32 nPropSize = sizeof(linkVersion); + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_PROTOCOL_VERSION, nPropSize, &linkVersion); + XN_IS_STATUS_OK_LOG_ERROR("Execute get protocol version command", nRetVal); + + if (nPropSize != sizeof(linkVersion)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of protocol version property: %u instead of %u", nPropSize, sizeof(linkVersion)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + xnLinkParseLeanVersion(version, linkVersion); + + xnLogInfo(XN_MASK_LINK, "LINK: Protocol version is %u.%u", version.m_nMajor, version.m_nMinor); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetHardwareVersion(XnUInt32& version) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting hardware version..."); + + XnUInt64 linkVersion; + nRetVal = GetIntProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_HW_VERSION, linkVersion); + XN_IS_STATUS_OK_LOG_ERROR("Execute get hardware version command", nRetVal); + + version = (XnUInt32)XN_PREPARE_VAR64_IN_BUFFER(linkVersion); + + xnLogInfo(XN_MASK_LINK, "LINK: Hardware version is %llu", version); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetSerialNumber(XnChar* strSerialNumber, XnUInt32 nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting serial number..."); + + XnLinkSerialNumber linkSerial; + XnUInt32 nPropSize = sizeof(linkSerial); + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_SERIAL_NUMBER, nPropSize, &linkSerial); + XN_IS_STATUS_OK_LOG_ERROR("Execute get serial version", nRetVal); + + if (nPropSize != sizeof(linkSerial)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of serial version property: %u instead of %u", nPropSize, sizeof(linkSerial)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = xnOSStrCopy(strSerialNumber, linkSerial.m_strSerialNumber, nSize); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Serial number is %s", strSerialNumber); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetComponentsVersions(xnl::Array& components) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting components versions..."); + + XnLinkComponentVersionsList* pComponentVersionsList = reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_COMPONENT_VERSIONS, nResponseSize, pComponentVersionsList); + XN_IS_STATUS_OK_LOG_ERROR("Execute get components versions list", nRetVal); + + nRetVal = xnLinkParseComponentVersionsList(components, pComponentVersionsList, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("parse components versions list", nRetVal); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::UploadFile(const XnChar* strFileName, XnBool bOverrideFactorySettings) +{ + XnStatus nRetVal = XN_STATUS_OK; + XN_FILE_HANDLE hFile = XN_INVALID_FILE_HANDLE; + XnUInt64 nFileSize = 0; + XnUInt32 nBytesRead = 0; + XnUInt32 nCunkSize = m_msgEncoder.GetMaxMsgSize(); + XnUInt8* pChunk = NULL; + XnUInt64 nBytesToSend = 0; + XnUInt32 nBytesInChunk = 0; + XnLinkFragmentation fragmentation = XN_LINK_FRAG_BEGIN; + + xnLogVerbose(XN_MASK_LINK, "LINK: Uploading file %s%s...", strFileName, bOverrideFactorySettings ? "[factory]" : ""); + + xnl::AutoMutexLocker mutexLocker(m_hMutex, MUTEX_TIMEOUT); + XN_IS_STATUS_OK_LOG_ERROR("Lock mutex", mutexLocker.GetStatus()); + + nRetVal = xnOSOpenFile(strFileName, XN_OS_FILE_READ, &hFile); + XN_IS_STATUS_OK_LOG_ERROR("Open file", nRetVal); + nRetVal = xnOSGetFileSize64(strFileName, &nFileSize); + XN_IS_STATUS_OK_LOG_ERROR("Get file size", nRetVal); + nBytesToSend = nFileSize + sizeof(XnLinkUploadFileHeader); + pChunk = (XnUInt8*)xnOSMallocAligned(nCunkSize, XN_DEFAULT_MEM_ALIGN); + if (pChunk == NULL) + { + xnOSCloseFile(&hFile); + xnLogError(XN_MASK_LINK, "LINK: Failed to allocate buffer of %u bytes for loading file", nCunkSize); + XN_ASSERT(FALSE); + return XN_STATUS_ALLOC_FAILED; + } + + while (nBytesToSend > 0) + { + nBytesInChunk = 0; + nBytesRead = nCunkSize; + + if (nBytesToSend == nFileSize + sizeof(XnLinkUploadFileHeader)) + { + // first packet + XnLinkUploadFileHeader* pUploadFileHeader = (XnLinkUploadFileHeader*)pChunk; + pUploadFileHeader->m_bOverrideFactorySettings = bOverrideFactorySettings; + + nBytesRead -= sizeof(XnLinkUploadFileHeader); + nBytesInChunk += sizeof(XnLinkUploadFileHeader); + } + + nRetVal = xnOSReadFile(hFile, pChunk + nBytesInChunk, &nBytesRead); + if ((nRetVal != XN_STATUS_OK) || (nBytesRead == 0)) + { + xnOSCloseFile(&hFile); + xnOSFreeAligned(pChunk); + xnLogError(XN_MASK_LINK, "LINK: Failed to read from file: %s", + (nBytesRead == 0) ? "0 bytes read" : xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return nRetVal; + } + + nBytesInChunk += nBytesRead; + + if (nBytesToSend <= nCunkSize) + { + //About to send last chunk - add END bit. + fragmentation = XnLinkFragmentation(fragmentation | XN_LINK_FRAG_END); + } + + xnLogVerbose(XN_MASK_LINK, "LINK: Sending file chunk..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnBool isLast; + nRetVal = ExecuteImpl(XN_LINK_MSG_UPLOAD_FILE, XN_LINK_STREAM_ID_NONE, pChunk, nBytesInChunk, fragmentation, m_pIncomingResponse, nResponseSize, TRUE, isLast); + if (nRetVal != XN_STATUS_OK) + { + xnOSCloseFile(&hFile); + xnOSFreeAligned(pChunk); + xnLogError(XN_MASK_LINK, "LINK: Failed to send data: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return nRetVal; + } + + /*If we need to send another chunk, its default fragmentation is MIDDLE (if needed it'll change to + END before sending). */ + fragmentation = XN_LINK_FRAG_MIDDLE; + + nBytesToSend -= nBytesInChunk; + } + + xnOSCloseFile(&hFile); + xnOSFreeAligned(pChunk); + xnLogInfo(XN_MASK_LINK, "LINK: File %s uploaded", strFileName); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetFileList(xnl::Array& files) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting file list..."); + + files.Clear(); + + XnLinkGetFileListResponse* pGetFileListResponse = reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_GET_FILE_LIST, XN_LINK_STREAM_ID_NONE, NULL, 0, pGetFileListResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute get file list command", nRetVal); + + if (nResponseSize < sizeof(pGetFileListResponse->m_nCount)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of 'get file list' response: %u (should be at least %u)", nResponseSize, sizeof(pGetFileListResponse->m_nCount)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + XnUInt32 nCount = XN_PREPARE_VAR32_IN_BUFFER(pGetFileListResponse->m_nCount); + if (nResponseSize < sizeof(pGetFileListResponse->m_nCount) + nCount*sizeof(XnLinkFileEntry)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of 'get file list' response: %u (should be at least %u)", nResponseSize, sizeof(pGetFileListResponse->m_nCount) + nCount*sizeof(XnLinkFileEntry)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = files.Reserve(nCount); + XN_IS_STATUS_OK(nRetVal); + + for (XnUInt32 i = 0; i < nCount; ++i) + { + XnLinkFileEntry* pLinkEntry = &pGetFileListResponse->m_aFileEntries[i]; + XnFwFileEntry entry; + xnOSStrCopy(entry.name, pLinkEntry->m_strName, sizeof(entry.name)); + entry.version.major = pLinkEntry->m_nVersion.m_nMajor; + entry.version.minor = pLinkEntry->m_nVersion.m_nMinor; + entry.version.maintenance = pLinkEntry->m_nVersion.m_nMaintenance; + entry.version.build = pLinkEntry->m_nVersion.m_nBuild; + entry.address = XN_PREPARE_VAR32_IN_BUFFER(pLinkEntry->m_nAddress); + entry.size = XN_PREPARE_VAR32_IN_BUFFER(pLinkEntry->m_nSize); + entry.crc = XN_PREPARE_VAR16_IN_BUFFER(pLinkEntry->m_nCRC); + entry.zone = XN_PREPARE_VAR16_IN_BUFFER(pLinkEntry->m_nZone); + entry.flags = (XnFwFileFlags)pLinkEntry->m_nFlags; + + nRetVal = files.AddLast(entry); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::DownloadFile(XnUInt16 zone, const XnChar* fwFileName, const XnChar* targetFile) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Downloading file %s from zone %u...", fwFileName, zone); + + XnLinkDownloadFileParams downloadFileParams = {0}; + nRetVal = xnOSStrCopy(downloadFileParams.m_strName, fwFileName, sizeof(downloadFileParams.m_strName)); + XN_IS_STATUS_OK_LOG_ERROR("Bad file name", nRetVal); + downloadFileParams.m_nZone = XN_PREPARE_VAR16_IN_BUFFER(zone); + + XN_FILE_HANDLE hTargetFile; + nRetVal = xnOSOpenFile(targetFile, XN_OS_FILE_WRITE | XN_OS_FILE_TRUNCATE, &hTargetFile); + XN_IS_STATUS_OK_LOG_ERROR("Open target file", nRetVal); + + XnUInt64 nStartTime; + xnOSGetHighResTimeStamp(&nStartTime); + XnUInt32 nFileSize = 0; + + XnBool isLast = FALSE; + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_DOWNLOAD_FILE, 0, &downloadFileParams, sizeof(downloadFileParams), m_pIncomingResponse, nResponseSize, &isLast); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "LINK: Failed to execute download file command: %s", xnGetStatusString(nRetVal)); + xnOSCloseFile(&hTargetFile); + return nRetVal; + } + + nRetVal = xnOSWriteFile(hTargetFile, m_pIncomingResponse, nResponseSize); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "LINK: Failed to write file: %s", xnGetStatusString(nRetVal)); + xnOSCloseFile(&hTargetFile); + return nRetVal; + } + + nFileSize += nResponseSize; + + while (!isLast) + { + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ContinueResponseImpl(XN_LINK_MSG_DOWNLOAD_FILE, 0, m_pIncomingResponse, nResponseSize, isLast); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "LINK: Failed to continue download file: %s", xnGetStatusString(nRetVal)); + xnOSCloseFile(&hTargetFile); + return nRetVal; + } + nFileSize += nResponseSize; + + nRetVal = xnOSWriteFile(hTargetFile, m_pIncomingResponse, nResponseSize); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "LINK: Failed to write file: %s", xnGetStatusString(nRetVal)); + xnOSCloseFile(&hTargetFile); + return nRetVal; + } + } + + XnUInt64 nEndTime; + xnOSGetHighResTimeStamp(&nEndTime); + + XnDouble totalTime = (nEndTime - nStartTime)/1e3; + + xnLogVerbose(XN_MASK_LINK, "LINK: Downloaded %u bytes from file %u/%s in %.2f ms (%.2f KB/s)", nFileSize, zone, fwFileName, totalTime, nFileSize / totalTime); + + xnOSCloseFile(&hTargetFile); + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::GetLogicalMaxPacketSize(XnUInt16& nMaxPacketSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt8 command[64]; + XnUInt8 response[64]; + XnUInt32 nResponseSize = 0; + XnUInt16 nResponseCode = 0; + xn::LinkPacketHeader* pCommandHeader = reinterpret_cast(command); + XnLinkGetPropParams* pGetPropParams = reinterpret_cast(command + sizeof(xn::LinkPacketHeader)); + + XnLinkResponseHeader* pResponseHeader = reinterpret_cast(response); + XnLinkGetPropResponse* pGetPropResponse = reinterpret_cast(response + sizeof(XnLinkResponseHeader)); + XnUInt64* pPropValue = reinterpret_cast(response + sizeof(XnLinkResponseHeader) + sizeof(XnLinkPropValHeader)); + XnUInt64 nTempPropValue = 0; + + xnLogVerbose(XN_MASK_LINK, "LINK: Link control endpoint - getting logical max packet size..."); + + //We encode this message ourselves, not with msg encoder, because we haven't yet initialized our message encoder + //(we need the size to initialize it...) + pCommandHeader->SetMagic(); + pCommandHeader->SetSize(sizeof(xn::LinkPacketHeader) + sizeof(XnLinkGetPropParams)); + pCommandHeader->SetMsgType(XN_LINK_MSG_GET_PROP); + pCommandHeader->SetFragmentationFlags(XN_LINK_FRAG_SINGLE); + pCommandHeader->SetStreamID(XN_LINK_STREAM_ID_NONE); + pCommandHeader->SetPacketID(m_nPacketID); + pCommandHeader->SetCID(0); + pGetPropParams->m_nPropID = XN_PREPARE_VAR16_IN_BUFFER(XN_LINK_PROP_ID_CONTROL_MAX_PACKET_SIZE); + pGetPropParams->m_nPropType = XN_PREPARE_VAR16_IN_BUFFER(XN_LINK_PROP_TYPE_INT); + + nRetVal = m_pConnection->Send(command, pCommandHeader->GetSize()); + XN_IS_STATUS_OK_LOG_ERROR("Get logical control max packet size ", nRetVal); + + nResponseSize = sizeof(response); + nRetVal = m_pConnection->Receive(response, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Receive response for get logical control max packet size command", nRetVal); + + nRetVal = ValidateResponsePacket(reinterpret_cast(&pResponseHeader->m_header), XN_LINK_MSG_GET_PROP, XN_LINK_STREAM_ID_NONE, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Validate response packet for get logical packet size", nRetVal); + + nResponseCode = XN_PREPARE_VAR16_IN_BUFFER(pResponseHeader->m_responseInfo.m_nResponseCode); + if (pResponseHeader->m_responseInfo.m_nResponseCode != XN_PREPARE_VAR16_IN_BUFFER(XN_LINK_RESPONSE_OK)) + { + xnLogError(XN_MASK_LINK, "LINK: Got response for get logical control max packet size: '%s' (%u)", xnLinkResponseCodeToStr(nResponseCode)); + XN_ASSERT(FALSE); + return xnLinkResponseCodeToStatus(nResponseCode); + } + + if (pGetPropResponse->m_header.m_nPropID != pGetPropParams->m_nPropID) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad prop id in response for get logical control max packet size"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + if (pGetPropResponse->m_header.m_nPropType != pGetPropParams->m_nPropType) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad prop type in response for get logical control max packet size"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + if (pGetPropResponse->m_header.m_nValueSize != XN_PREPARE_VAR32_IN_BUFFER(sizeof(XnUInt64))) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad value size in response for get logical control max packet size"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + nTempPropValue = XN_PREPARE_VAR64_IN_BUFFER(*pPropValue); + if (nTempPropValue > XN_MAX_UINT16) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad value for logical max packet size"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + nMaxPacketSize = static_cast(nTempPropValue); + xnLogVerbose(XN_MASK_LINK, "LINK: Link control endpoint logical max packet size is %u bytes", nMaxPacketSize); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::ExecuteImpl(XnUInt16 nMsgType, + XnUInt16 nStreamID, + const void* pData, + XnUInt32 nSize, + XnLinkFragmentation fragmentation, + void* pResponseData, + XnUInt32& nResponseSize, + XnBool autoContinue, + XnBool& isLast) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nReceivedResponsePacketSize = 0; + XnUInt16 nPacketSize = 0; + XnLinkFragmentation responseFragmentation = XN_LINK_FRAG_MIDDLE; + + //Before we start encoding the command we first make sure it is supported + if (!IsMsgTypeSupported(nMsgType)) + { + xnLogWarning(XN_MASK_LINK, "LINK: Msg type 0x%04X is not in supported msg types", nMsgType); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_CMD_NOT_SUPPORTED; + } + + /* First step - encode command into separate packets. */ + //Keep only the BEGIN bit of the fragmentation mask for the first packet. + m_msgEncoder.BeginEncoding(nMsgType, m_nPacketID, nStreamID, XnLinkFragmentation(fragmentation & XN_LINK_FRAG_BEGIN)); + m_msgEncoder.EncodeData(pData, nSize); + m_msgEncoder.EndEncoding(XnLinkFragmentation(fragmentation & XN_LINK_FRAG_END)); + + XnUInt32 nBytesLeftToSend = m_msgEncoder.GetEncodedSize(); + union + { + const XnUInt8* pRawCommandPacket; + XnLinkPacket* pCommandPacket; + }; + pRawCommandPacket = reinterpret_cast(m_msgEncoder.GetEncodedData()); + + /* Second step - Send each packet and get a response for it. */ + while (nBytesLeftToSend > 0) + { + nPacketSize = static_cast(XN_MIN(nBytesLeftToSend, m_nMaxPacketSize)); + nRetVal = m_pConnection->Send(pRawCommandPacket, nPacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Send control packet", nRetVal); + pRawCommandPacket += nPacketSize; + nBytesLeftToSend -= nPacketSize; + nReceivedResponsePacketSize = m_nMaxPacketSize; + + nRetVal = m_pConnection->Receive(m_pIncomingRawPacket, nReceivedResponsePacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Receive response packet", nRetVal); + XN_ASSERT(nReceivedResponsePacketSize < XN_MAX_UINT16); + nRetVal = ValidateResponsePacket(m_pIncomingPacket, nMsgType, nStreamID, nReceivedResponsePacketSize); + responseFragmentation = m_pIncomingPacket->GetFragmentationFlags(); + XN_IS_STATUS_OK_LOG_ERROR("Parse response packet header", nRetVal); + nRetVal = m_responseMsgParser.BeginParsing(pResponseData, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Begin parsing response packet", nRetVal); + nRetVal = m_responseMsgParser.ParsePacket(*m_pIncomingPacket, m_pIncomingRawPacket + sizeof(XnLinkPacketHeader)); + XN_IS_STATUS_OK_LOG_ERROR("Parse response packet", nRetVal); + + if (nBytesLeftToSend > 0) + { + if (responseFragmentation != XN_LINK_FRAG_SINGLE) + { + xnLogWarning(XN_MASK_LINK, "LINK: Got unexpected responseFragmentation flag of 0x%X in response when there are still more packets to be sent as part of current command", responseFragmentation); + XN_ASSERT(FALSE); + //This is just a warning - we keep going. + } + + if (m_responseMsgParser.GetParsedSize() > 0) + { + xnLogWarning(XN_MASK_LINK, "LINK: Got unexpected response packet size of %u in response when there are still more packets to be sent as part of current command", m_responseMsgParser.GetParsedSize()); + XN_ASSERT(FALSE); + //This is just a warning - we keep going. + } + } + + /* Advance packet ID for next packet*/ + m_nPacketID++; + } + + XnUInt32 nTotalResponseSize = m_responseMsgParser.GetParsedSize(); + XnUInt8* pResponseBytes = reinterpret_cast(pResponseData); + + /* Third step - If needed, continue receiving the rest of the response, in separate packets. */ + isLast = ((responseFragmentation & XN_LINK_FRAG_END) == XN_LINK_FRAG_END); + while (autoContinue && !isLast) + { + XnUInt32 nPacketResponseSize = nResponseSize - nTotalResponseSize; + nRetVal = ContinueResponseImpl(nMsgType, nStreamID, pResponseBytes + nTotalResponseSize, nPacketResponseSize, isLast); + XN_IS_STATUS_OK_LOG_ERROR("Continue response", nRetVal); + + nTotalResponseSize += nPacketResponseSize; + } + + nResponseSize = nTotalResponseSize; + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::ContinueResponseImpl(XnUInt16 originalMsgType, XnUInt16 streamID, void* pResponseData, XnUInt32& nResponseSize, XnBool& outLastPacket) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Asking for additional data for response..."); + + XnLinkContinueReponseParams continueResponseParams; + continueResponseParams.m_nOriginalMsgType = XN_PREPARE_VAR16_IN_BUFFER(originalMsgType); + + //Encode link continue response command with m_msgEncoder. + m_msgEncoder.BeginEncoding(XN_LINK_MSG_CONTINUE_REPONSE, m_nPacketID, 0); + m_msgEncoder.EncodeData(&continueResponseParams, sizeof(continueResponseParams)); + m_msgEncoder.EndEncoding(); + + //Send Continue Response Command to device + nRetVal = m_pConnection->Send(m_msgEncoder.GetEncodedData(), m_msgEncoder.GetEncodedSize()); + XN_IS_STATUS_OK_LOG_ERROR("Send Continue Response command", nRetVal); + + //Receive one more response packet + XnUInt32 nReceivedResponsePacketSize = m_nMaxPacketSize; + nRetVal = m_pConnection->Receive(m_pIncomingRawPacket, nReceivedResponsePacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Receive response packet", nRetVal); + XN_ASSERT(nReceivedResponsePacketSize <= m_nMaxPacketSize); + + //Now expecting continue response message type + nRetVal = ValidateResponsePacket(m_pIncomingPacket, XN_LINK_MSG_CONTINUE_REPONSE, streamID, nReceivedResponsePacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Parse response packet header", nRetVal); + XnLinkFragmentation responseFragmentation = m_pIncomingPacket->GetFragmentationFlags(); + + nRetVal = m_responseMsgParser.BeginParsing(pResponseData, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Begin parsing response packet", nRetVal); + nRetVal = m_responseMsgParser.ParsePacket(*m_pIncomingPacket, m_pIncomingRawPacket + sizeof(XnLinkPacketHeader)); + XN_IS_STATUS_OK_LOG_ERROR("Parse response packet", nRetVal); + + /* Advance packet ID for next packet*/ + m_nPacketID++; + + nResponseSize = m_responseMsgParser.GetParsedSize(); + outLastPacket = ((responseFragmentation & XN_LINK_FRAG_END) == XN_LINK_FRAG_END); + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::StartStreaming(XnUInt16 nStreamID) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nResponseSize = m_nMaxResponseSize; + + xnLogVerbose(XN_MASK_LINK, "LINK: Starting streaming for stream %u...", nStreamID); + + nRetVal = ExecuteCommand(XN_LINK_MSG_START_STREAMING, nStreamID, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute start streaming command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u has started streaming.", nStreamID); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::StopStreaming(XnUInt16 nStreamID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Stopping streaming for stream %u...", nStreamID); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_STOP_STREAMING, nStreamID, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute stop streaming command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u has stopped streaming.", nStreamID); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::SoftReset() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Executing soft reset..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_SOFT_RESET, XN_LINK_STREAM_ID_NONE, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute soft reset", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Soft reset done."); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::HardReset() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Executing power reset..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_HARD_RESET, XN_LINK_STREAM_ID_NONE, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute hard reset", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Power reset done."); + + return XN_STATUS_OK; +} + + +XnStatus LinkControlEndpoint::GetSupportedI2CDevices(xnl::Array& supportedDevices) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting I2C devices list..."); + + XnLinkSupportedI2CDevices* pSupportedI2CDevices = + reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_SUPPORTED_I2C_DEVICES, nResponseSize, pSupportedI2CDevices); + XN_IS_STATUS_OK_LOG_ERROR("Execute get supported I2C devices command", nRetVal); + + nRetVal = xnLinkParseSupportedI2CDevices(pSupportedI2CDevices, nResponseSize, supportedDevices); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetSupportedLogFiles(xnl::Array& supportedFiles) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting supported log files list..."); + + XnLinkSupportedLogFiles* pSupportedLogFiles = + reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_SUPPORTED_LOG_FILES, nResponseSize, pSupportedLogFiles); + XN_IS_STATUS_OK_LOG_ERROR("Execute get supported log files command", nRetVal); + + nRetVal = xnLinkParseSupportedLogFiles(pSupportedLogFiles, nResponseSize, supportedFiles); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + + +XnStatus LinkControlEndpoint::GetSupportedBistTests(xnl::Array& supportedTests) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting supported BIST tests list..."); + + XnLinkSupportedBistTests* pSupportedBistTests = + reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_SUPPORTED_BIST_TESTS, nResponseSize, pSupportedBistTests); + XN_IS_STATUS_OK_LOG_ERROR("Execute get supported bist tests command", nRetVal); + + nRetVal = xnLinkParseSupportedBistTests(pSupportedBistTests, nResponseSize, supportedTests); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::ExecuteBistTests(XnUInt32 nID, uint32_t& errorCode, uint32_t& extraDataSize, uint8_t* extraData) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Executing BIST %u...", nID); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + + XnLinkExecuteBistParams executeBistParams; + executeBistParams.m_nID = XN_PREPARE_VAR32_IN_BUFFER(nID); + + nRetVal = ExecuteCommand(XN_LINK_MSG_EXECUTE_BIST_TESTS, 0, &executeBistParams, sizeof(executeBistParams), + m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute BIST command", nRetVal); + + XnLinkExecuteBistResponse* pExecuteBistResponse = (XnLinkExecuteBistResponse*)m_pIncomingResponse; + + if (nResponseSize < sizeof(pExecuteBistResponse->m_nErrorCode) + sizeof(pExecuteBistResponse->m_nExtraDataSize)) + { + xnLogError(XN_MASK_LINK, "LINK: Response struct for test is smaller than header (%u instead of %u)", nResponseSize, sizeof(pExecuteBistResponse->m_nErrorCode) + sizeof(pExecuteBistResponse->m_nExtraDataSize)); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + if (extraDataSize < nResponseSize) + { + xnLogError(XN_MASK_LINK, "LINK: Response struct for test is too small (%u instead of %u)", extraDataSize, nResponseSize); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + XnUInt32 nExtraDataSize = XN_PREPARE_VAR32_IN_BUFFER(pExecuteBistResponse->m_nExtraDataSize); + if (nExtraDataSize > nResponseSize - sizeof(pExecuteBistResponse->m_nErrorCode) - sizeof(pExecuteBistResponse->m_nExtraDataSize)) + { + xnLogError(XN_MASK_LINK, "LINK: Extra data size is invalid (%u. response size: %u)", nExtraDataSize, nResponseSize); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + errorCode = XN_PREPARE_VAR32_IN_BUFFER(pExecuteBistResponse->m_nErrorCode); + extraDataSize = nExtraDataSize; + xnOSMemCopy(extraData, pExecuteBistResponse->m_ExtraData, nExtraDataSize); + + xnLogInfo(XN_MASK_LINK, "LINK: BIST %u completed with error code %u", nID, errorCode); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::WriteI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32 nValue, XnUInt32 nMask) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Writing to I2C device %u...", nDeviceID); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnLinkWriteI2CParams writeI2CParams; + + writeI2CParams.m_nDeviceID = nDeviceID; + writeI2CParams.m_nAddressSize = nAddressSize; + writeI2CParams.m_nValueSize = nValueSize; + writeI2CParams.m_nReserved = 0; + writeI2CParams.m_nAddress = XN_PREPARE_VAR32_IN_BUFFER(nAddress); + writeI2CParams.m_nValue = XN_PREPARE_VAR32_IN_BUFFER(nValue); + writeI2CParams.m_nMask = XN_PREPARE_VAR32_IN_BUFFER(nMask); + nRetVal = ExecuteCommand(XN_LINK_MSG_WRITE_I2C, XN_LINK_STREAM_ID_NONE, &writeI2CParams, sizeof(writeI2CParams), m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute Write I2C command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: I2C writing completed"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::ReadI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32& nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Reading from I2C device %u...", nDeviceID); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnLinkReadI2CParams readI2CParams; + XnLinkReadI2CResponse* pReadI2CResponse = reinterpret_cast(m_pIncomingResponse); + + readI2CParams.m_nDeviceID = nDeviceID; + readI2CParams.m_nAddressSize = nAddressSize; + readI2CParams.m_nValueSize = nValueSize; + readI2CParams.m_nAddress = XN_PREPARE_VAR32_IN_BUFFER(nAddress); + nRetVal = ExecuteCommand(XN_LINK_MSG_READ_I2C, XN_LINK_STREAM_ID_NONE, &readI2CParams, sizeof(readI2CParams), pReadI2CResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute Read I2C command", nRetVal); + if (nResponseSize != sizeof(XnLinkReadI2CResponse)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of readI2C response: %u instead of %u", nResponseSize, sizeof(XnLinkReadI2CResponse)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + nValue = XN_PREPARE_VAR32_IN_BUFFER(pReadI2CResponse->m_nValue); + + xnLogInfo(XN_MASK_LINK, "LINK: I2C reading completed"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::WriteAHB(XnUInt32 nAddress, XnUInt32 nValue, XnUInt8 nBitOffset, XnUInt8 nBitWidth) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Writing to AHB register..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnLinkWriteAHBParams writeAHBParams; + writeAHBParams.m_nAddress = XN_PREPARE_VAR32_IN_BUFFER(nAddress); + writeAHBParams.m_nValue = XN_PREPARE_VAR32_IN_BUFFER(nValue); + writeAHBParams.m_nBitOffset = nBitOffset; + writeAHBParams.m_nBitWidth = nBitWidth; + nRetVal = ExecuteCommand(XN_LINK_MSG_WRITE_AHB, XN_LINK_STREAM_ID_NONE, &writeAHBParams, sizeof(writeAHBParams), m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute Write AHB command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: AHB writing completed"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::ReadAHB(XnUInt32 nAddress, XnUInt8 nBitOffset, XnUInt8 nBitWidth, XnUInt32& nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Reading from AHB register..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnLinkReadAHBParams readAHBParams; + XnLinkReadAHBResponse* pReadAHBResponse = reinterpret_cast(m_pIncomingResponse); + readAHBParams.m_nAddress = XN_PREPARE_VAR32_IN_BUFFER(nAddress); + readAHBParams.m_nBitOffset = nBitOffset; + readAHBParams.m_nBitWidth = nBitWidth; + nRetVal = ExecuteCommand(XN_LINK_MSG_READ_AHB, XN_LINK_STREAM_ID_NONE, &readAHBParams, sizeof(readAHBParams), pReadAHBResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute read AHB command", nRetVal); + if (nResponseSize != sizeof(XnLinkReadAHBResponse)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of ReadAHB response: %u instead of %u", nResponseSize, sizeof(XnLinkReadAHBResponse)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + nValue = XN_PREPARE_VAR32_IN_BUFFER(pReadAHBResponse->m_nValue); + + xnLogInfo(XN_MASK_LINK, "LINK: AHB reading completed"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetShiftToDepthConfig(XnUInt16 nStreamID, XnShiftToDepthConfig& shiftToDepthConfig) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting shift-to-depth configuration..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnLinkGetShiftToDepthConfigResponse* pGetS2DConfigResponse = + reinterpret_cast(m_pIncomingResponse); + XnLinkShiftToDepthConfig* pLinkS2DConfig = &(pGetS2DConfigResponse->m_config); + nRetVal = ExecuteCommand(XN_LINK_MSG_GET_S2D_CONFIG, nStreamID, NULL, 0, + pGetS2DConfigResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute get s2d config command", nRetVal); + + xnLinkParseShiftToDepthConfig(shiftToDepthConfig, *pLinkS2DConfig); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::SetVideoMode(XnUInt16 nStreamID, const XnFwStreamVideoMode& videoMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Setting video mode for stream %u...", nStreamID); + + XnLinkVideoMode linkVideoMode; + xnLinkEncodeVideoMode(linkVideoMode, videoMode); + nRetVal = SetGeneralProperty(nStreamID, XN_LINK_PROP_ID_VIDEO_MODE, sizeof(linkVideoMode), &linkVideoMode); + XN_IS_STATUS_OK_LOG_ERROR("set map output mode property", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Video mode set for stream %u", nStreamID); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetVideoMode(XnUInt16 nStreamID, XnFwStreamVideoMode& videoMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting video mode for stream %u...", nStreamID); + + XnLinkVideoMode linkVideoMode; + XnUInt32 nPropSize = sizeof(linkVideoMode); + + nRetVal = GetGeneralProperty(nStreamID, XN_LINK_PROP_ID_VIDEO_MODE, nPropSize, &linkVideoMode); + XN_IS_STATUS_OK_LOG_ERROR("Get map output mode property", nRetVal); + + if (nPropSize != sizeof(linkVideoMode)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of link map output mode: %u instead of %u", nPropSize, sizeof(linkVideoMode)); + XN_ASSERT(FALSE); + return XN_STATUS_INVALID_BUFFER_SIZE; + } + + xnLinkParseVideoMode(videoMode, linkVideoMode); + + XnChar strVideoMode[200]; + xnLinkVideoModeToString(videoMode, strVideoMode, sizeof(strVideoMode)); + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u video mode: %s", nStreamID, strVideoMode); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetSupportedVideoModes(XnUInt16 nStreamID, + xnl::Array& supportedVideoModes) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting supported video modes for stream %u...", nStreamID); + + XnUInt8 supportedMapOutputModesBuff[MAX_PROP_SIZE]; + XnUInt32 nPropSize = sizeof(supportedMapOutputModesBuff); + XnLinkSupportedVideoModes* pLinkSupportedMapOutputModes = reinterpret_cast(supportedMapOutputModesBuff); + XnUInt32 nModes = 0; + XnUInt32 nExpectedPropSize = 0; + + nRetVal = GetGeneralProperty(nStreamID, XN_LINK_PROP_ID_SUPPORTED_VIDEO_MODES, nPropSize, pLinkSupportedMapOutputModes); + XN_IS_STATUS_OK_LOG_ERROR("Execute Get Map Output Mode Command", nRetVal); + nModes = XN_PREPARE_VAR32_IN_BUFFER(pLinkSupportedMapOutputModes->m_nNumModes); + nExpectedPropSize = (sizeof(pLinkSupportedMapOutputModes->m_nNumModes) + + (sizeof(pLinkSupportedMapOutputModes->m_supportedVideoModes[0]) * nModes)); + if (nPropSize != nExpectedPropSize) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of 'supported map output modes' property: %u instead of %u", nPropSize, nExpectedPropSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = supportedVideoModes.SetSize(nModes); + XN_IS_STATUS_OK_LOG_ERROR("Set size of output supported map output modes array", nRetVal); + for (XnUInt32 i = 0; i < nModes; i++) + { + xnLinkParseVideoMode(supportedVideoModes[i], pLinkSupportedMapOutputModes->m_supportedVideoModes[i]); + } + + return XN_STATUS_OK; +} + + +XnStatus LinkControlEndpoint::EnumerateStreams(xnl::Array& aStreamInfos) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting the list of supported streams..."); + + XnLinkEnumerateStreamsResponse* pEnumerateNodesResponse = + reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnUInt32 nExpectedResponseSize = 0; + XnUInt32 nNumNodes = 0; + + nRetVal = ExecuteCommand(XN_LINK_MSG_ENUMERATE_STREAMS, XN_LINK_STREAM_ID_NONE, NULL, 0, + pEnumerateNodesResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute enumerate nodes command", nRetVal); + if (nResponseSize < sizeof(pEnumerateNodesResponse->m_nNumStreams)) + { + xnLogError(XN_MASK_LINK, "LINK: Got insufficient bytes in enumerate nodes response"); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + nNumNodes = XN_PREPARE_VAR32_IN_BUFFER(pEnumerateNodesResponse->m_nNumStreams); + nExpectedResponseSize = (sizeof(pEnumerateNodesResponse->m_nNumStreams) + + (nNumNodes * sizeof(pEnumerateNodesResponse->m_streamInfos[0]))); + if (nResponseSize != nExpectedResponseSize) + { + xnLogError(XN_MASK_LINK, "LINK: Got incorrect size of enumerate nodes response: expected %u but got %u", + nExpectedResponseSize, nResponseSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = aStreamInfos.SetSize(nNumNodes); + XN_IS_STATUS_OK_LOG_ERROR("Allocate node infos array", nRetVal); + for (XnUInt32 i = 0; i < nNumNodes; i++) + { + aStreamInfos[i].type = (XnFwStreamType)XN_PREPARE_VAR32_IN_BUFFER(pEnumerateNodesResponse->m_streamInfos[i].m_nStreamType); + XN_COMPILER_ASSERT(sizeof(aStreamInfos[i].creationInfo) >= sizeof(pEnumerateNodesResponse->m_streamInfos[i].m_strCreationInfo)); + xnOSStrCopy(aStreamInfos[i].creationInfo, + pEnumerateNodesResponse->m_streamInfos[i].m_strCreationInfo, + sizeof(aStreamInfos[i].creationInfo)); + } + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::CreateInputStream(XnStreamType streamType, const XnChar* strCreationInfo, XnUInt16& nStreamID, XnUInt16& nEndpointID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Creating stream..."); + + XnLinkCreateStreamParams createStreamParams = {0}; + XnLinkCreateStreamResponse* pCreateStreamResponse = reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + createStreamParams.m_nStreamType = XN_PREPARE_VAR32_IN_BUFFER(streamType); + xnOSStrCopy(createStreamParams.m_strCreationInfo, strCreationInfo, sizeof(createStreamParams.m_strCreationInfo)); + nRetVal = ExecuteCommand(XN_LINK_MSG_CREATE_STREAM, XN_LINK_STREAM_ID_NONE, &createStreamParams, sizeof(createStreamParams), + pCreateStreamResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute create stream command", nRetVal); + if (nResponseSize != sizeof(*pCreateStreamResponse)) + { + xnLogError(XN_MASK_LINK, "LINK: Got incorrect size of create nodes response: got %u but expected %u.", + nResponseSize, sizeof(*pCreateStreamResponse)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nStreamID = XN_PREPARE_VAR16_IN_BUFFER(pCreateStreamResponse->m_nStreamID); + nEndpointID = XN_PREPARE_VAR16_IN_BUFFER(pCreateStreamResponse->m_nEndpointID); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u created on endpoint %u", nStreamID, nEndpointID); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::DestroyInputStream(XnUInt16 nStreamID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Destroying stream %u...", nStreamID); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_DESTROY_STREAM, nStreamID, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute destroy stream command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u destroyed", nStreamID); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::SetProperty(XnUInt16 nStreamID, XnLinkPropType propType, XnLinkPropID propID, XnUInt32 nSize, const void* pSource) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nResponseSize = m_nMaxResponseSize; + + const XnUInt32 nMaxSize = 500; + XN_ASSERT(nSize < nMaxSize); + + XnUChar message[nMaxSize]; + + XnLinkPropVal* pSetPropParams = (XnLinkPropVal*)message; + pSetPropParams->m_header.m_nPropType = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)propType); + pSetPropParams->m_header.m_nPropID = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)propID); + pSetPropParams->m_header.m_nValueSize = XN_PREPARE_VAR32_IN_BUFFER(nSize); + xnOSMemCopy(pSetPropParams->m_value, pSource, nSize); + nRetVal = ExecuteCommand(XN_LINK_MSG_SET_PROP, nStreamID, pSetPropParams, sizeof(pSetPropParams->m_header) + nSize, + m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute set property command", nRetVal); + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetProperty(XnUInt16 nStreamID, XnLinkPropType propType, XnLinkPropID propID, XnUInt32& nSize, void* pDest) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nResponseSize = m_nMaxResponseSize; + + XnLinkGetPropParams getPropParams; + getPropParams.m_nPropType = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)propType); + getPropParams.m_nPropID = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)propID); + nRetVal = ExecuteCommand(XN_LINK_MSG_GET_PROP, nStreamID, &getPropParams, sizeof(getPropParams), + m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute get property command", nRetVal); + + XnLinkGetPropResponse* pResponse = (XnLinkGetPropResponse*)m_pIncomingResponse; + XnUInt32 nValueSize = XN_PREPARE_VAR32_IN_BUFFER(pResponse->m_header.m_nValueSize); + + if (nSize < nValueSize) + { + xnLogError(XN_MASK_LINK, "LINK: Got incorrect size for property: got %u but expected a max of %u.", + nValueSize, nSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + xnOSMemCopy(pDest, pResponse->m_value, nValueSize); + nSize = nValueSize; + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::SetIntProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt64 nValue) +{ + XnUInt64 nProtocolValue = XN_PREPARE_VAR64_IN_BUFFER(nValue); + return SetProperty(nStreamID, XN_LINK_PROP_TYPE_INT, propID, sizeof(nProtocolValue), &nProtocolValue); +} + +XnStatus LinkControlEndpoint::GetIntProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt64& nValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnUInt64 nProtocolValue; + XnUInt32 nValueSize = sizeof(nProtocolValue); + nRetVal = GetProperty(nStreamID, XN_LINK_PROP_TYPE_INT, propID, nValueSize, &nProtocolValue); + XN_IS_STATUS_OK(nRetVal); + + if (nValueSize != sizeof(nProtocolValue)) + { + xnLogError(XN_MASK_LINK, "LINK: Got incorrect size for int property: got %u but expected %u.", + nValueSize, sizeof(nProtocolValue)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nValue = XN_PREPARE_VAR64_IN_BUFFER(nProtocolValue); + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::SetRealProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnDouble dValue) +{ + XnDouble dProtocolValue = XN_PREPARE_VAR_FLOAT_IN_BUFFER(dValue); + return SetProperty(nStreamID, XN_LINK_PROP_TYPE_REAL, propID, sizeof(dProtocolValue), &dProtocolValue); +} + +XnStatus LinkControlEndpoint::GetRealProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnDouble& dValue) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDouble dProtocolValue = 0; + XnUInt32 nValueSize = sizeof(dProtocolValue); + nRetVal = GetProperty(nStreamID, XN_LINK_PROP_TYPE_REAL, propID, nValueSize, &dProtocolValue); + XN_IS_STATUS_OK(nRetVal); + + if (nValueSize != sizeof(dProtocolValue)) + { + xnLogError(XN_MASK_LINK, "LINK: Got incorrect size for int property: got %u but expected %u.", + nValueSize, sizeof(dProtocolValue)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + dValue = XN_PREPARE_VAR_FLOAT_IN_BUFFER(dProtocolValue); + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::SetStringProperty(XnUInt16 nStreamID, XnLinkPropID propID, const XnChar* strValue) +{ + return SetProperty(nStreamID, XN_LINK_PROP_TYPE_STRING, propID, xnOSStrLen(strValue)+1, strValue); +} + +XnStatus LinkControlEndpoint::GetStringProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt32 nSize, XnChar* strValue) +{ + return GetProperty(nStreamID, XN_LINK_PROP_TYPE_STRING, propID, nSize, strValue); +} + +XnStatus LinkControlEndpoint::SetGeneralProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt32 nSize, const void* pSource) +{ + return SetProperty(nStreamID, XN_LINK_PROP_TYPE_GENERAL, propID, nSize, pSource); +} + +XnStatus LinkControlEndpoint::GetGeneralProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt32& nSize, void* pDest) +{ + return GetProperty(nStreamID, XN_LINK_PROP_TYPE_GENERAL, propID, nSize, pDest); +} + +XnStatus LinkControlEndpoint::GetBitSetProperty(XnUInt16 nStreamID, XnLinkPropID propID, xnl::BitSet& bitSet) +{ + XnStatus nRetVal = XN_STATUS_OK; + + static const XnUInt32 MAX_SIZE = 512; + XnChar strBuffer[MAX_SIZE]; + XnUInt32 nSize = MAX_SIZE; + XnLinkBitSet* pBitSet = (XnLinkBitSet*)strBuffer; + + nRetVal = GetGeneralProperty(nStreamID, propID, nSize, pBitSet); + XN_IS_STATUS_OK(nRetVal); + + // check header + if (nSize < sizeof(pBitSet->m_nSize)) + { + xnLogError(XN_MASK_LINK, "LINK: Bad property value - bit set has no header!"); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + XnUInt32 nBitSetSize = XN_PREPARE_VAR32_IN_BUFFER(pBitSet->m_nSize); + + // check all data is here + if ((nSize - sizeof(pBitSet->m_nSize)) < nBitSetSize) + { + xnLogError(XN_MASK_LINK, "LINK: Bad property value - bit set size should be %u, but got only %u.", nBitSetSize, nSize - sizeof(pBitSet->m_nSize)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = bitSet.SetData(pBitSet->m_aData, nBitSetSize); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::GetCameraIntrinsics(XnUInt16 nStreamID, XnLinkCameraIntrinsics& cameraIntrinsics) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting camera intrinsics for stream %u...", nStreamID); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnLinkCameraIntrinsics* pLinkIntrinsics = reinterpret_cast(m_pIncomingResponse); + + nRetVal = ExecuteCommand(XN_LINK_MSG_GET_CAMERA_INTRINSICS, nStreamID, NULL, 0, pLinkIntrinsics, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute get FOV command", nRetVal); + + if (nResponseSize != sizeof(XnLinkCameraIntrinsics)) + { + xnLogError(XN_MASK_LINK, "LINK: Got bad size of get fov response: %u instead of %u", nResponseSize, sizeof(XnLinkCameraIntrinsics)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + cameraIntrinsics.m_nOpticalCenterX = XN_PREPARE_VAR16_IN_BUFFER(pLinkIntrinsics->m_nOpticalCenterX); + cameraIntrinsics.m_nOpticalCenterY = XN_PREPARE_VAR16_IN_BUFFER(pLinkIntrinsics->m_nOpticalCenterY); + cameraIntrinsics.m_fEffectiveFocalLengthInPixels = XN_PREPARE_VAR_FLOAT_IN_BUFFER(pLinkIntrinsics->m_fEffectiveFocalLengthInPixels); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::ValidateResponsePacket(const LinkPacketHeader* pPacketHeader, + XnUInt16 nExpectedMsgType, + XnUInt16 nExpectedStreamID, + XnUInt32 nBytesToRead) +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = pPacketHeader->Validate(nBytesToRead); + XN_IS_STATUS_OK_LOG_ERROR("Validate response packet header", nRetVal); + + if (pPacketHeader->GetMsgType() != nExpectedMsgType) + { + xnLogError(XN_MASK_LINK, "LINK: Expected msg type of 0x%X but got 0x%X", nExpectedMsgType, pPacketHeader->GetMsgType()); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_RESPONSE_MSG_TYPE_MISMATCH; + } + + if (pPacketHeader->GetStreamID() != nExpectedStreamID) + { + xnLogError(XN_MASK_LINK, "LINK: Got response packet for stream %u but expected stream %u", pPacketHeader->GetStreamID(), nExpectedStreamID); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_STREAM_ID; + } + + if (pPacketHeader->GetPacketID() != m_nPacketID) + { + xnLogError(XN_MASK_LINK, "LINK: Expected packet ID of %u in response but got %u on stream %u", + m_nPacketID, pPacketHeader->GetPacketID(), pPacketHeader->GetStreamID()); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_PACKETS_LOST; + } + + if (pPacketHeader->GetSize() < sizeof(XnLinkResponseHeader)) + { + xnLogError(XN_MASK_LINK, "LINK: Response packet size of %u is too small - min response packet size is %u", + pPacketHeader->GetSize(), sizeof(XnLinkResponseHeader)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_HEADER_SIZE; + } + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::BeginUpload() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Beginning upload session..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_BEGIN_UPLOAD, XN_LINK_STREAM_ID_NONE, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute begin upload command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Upload session started"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::EndUpload() +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Ending upload session..."); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + nRetVal = ExecuteCommand(XN_LINK_MSG_END_UPLOAD, XN_LINK_STREAM_ID_NONE, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute end upload command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Upload session ended"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::SetCropping(XnUInt16 nStreamID, const OniCropping& cropping) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Setting cropping for stream %u...", nStreamID); + + XnLinkCropping linkCropping; + linkCropping.m_bEnabled = XnUInt8(cropping.enabled); + linkCropping.m_nReserved1 = 0; + linkCropping.m_nReserved2 = 0; + linkCropping.m_nReserved3 = 0; + linkCropping.m_nXOffset = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.originX); + linkCropping.m_nYOffset = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.originY); + linkCropping.m_nXSize = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.width); + linkCropping.m_nYSize = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.height); + + nRetVal = SetGeneralProperty(nStreamID, XN_LINK_PROP_ID_CROPPING, sizeof(linkCropping), &linkCropping); + XN_IS_STATUS_OK_LOG_ERROR("Set cropping property", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u cropping set", nStreamID); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetCropping(XnUInt16 nStreamID, OniCropping& cropping) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting cropping for stream %u...", nStreamID); + + XnLinkCropping linkCropping; + XnUInt32 nPropSize = sizeof(linkCropping); + + nRetVal = GetGeneralProperty(nStreamID, XN_LINK_PROP_ID_CROPPING, nPropSize, &linkCropping); + XN_IS_STATUS_OK_LOG_ERROR("Get cropping property", nRetVal); + if (nPropSize != sizeof(linkCropping)) + { + xnLogError(XN_MASK_LINK, "LINK: Incorrect size of cropping data: expected %u but got %u", sizeof(linkCropping), nPropSize); + XN_ASSERT(FALSE); + return XN_STATUS_INVALID_BUFFER_SIZE; + } + + cropping.enabled = linkCropping.m_bEnabled; + cropping.originX = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nXOffset); + cropping.originY = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nYOffset); + cropping.width = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nXSize); + cropping.height = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nYSize); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetSupportedMsgTypes(xnl::Array& supportedMsgTypes) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting supported message types..."); + + XnUInt8 supportedMsgTypesBuff[MAX_PROP_SIZE]; + XnUInt32 nBufferSize = sizeof(supportedMsgTypesBuff); + nRetVal = GetGeneralProperty(XN_LINK_STREAM_ID_NONE, XN_LINK_PROP_ID_SUPPORTED_MSG_TYPES, nBufferSize, supportedMsgTypesBuff); + XN_IS_STATUS_OK_LOG_ERROR("Get supported msg types property", nRetVal); + nRetVal = xnLinkParseIDSet(supportedMsgTypes, supportedMsgTypesBuff, nBufferSize); + XN_IS_STATUS_OK_LOG_ERROR("Parse supported msg types", nRetVal); + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetSupportedProperties(xnl::Array& supportedProps) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting supported properties..."); + + XnUInt8 supportedPropsBuff[MAX_PROP_SIZE]; + XnUInt32 nBufferSize = sizeof(supportedPropsBuff); + nRetVal = GetGeneralProperty(XN_LINK_STREAM_ID_NONE, XN_LINK_PROP_ID_SUPPORTED_PROPS, nBufferSize, supportedPropsBuff); + XN_IS_STATUS_OK_LOG_ERROR("Get supported msg types property", nRetVal); + nRetVal = xnLinkParseIDSet(supportedProps, supportedPropsBuff, nBufferSize); + XN_IS_STATUS_OK_LOG_ERROR("Parse supported msg types", nRetVal); + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetSupportedInterfaces(XnUInt16 nStreamID, xnl::BitSet& supportedInterfaces) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting supported interfaces for stream %u...", nStreamID); + + XnUInt8 supportedInterfacesBuff[MAX_PROP_SIZE]; + XnUInt32 nBufferSize = sizeof(supportedInterfacesBuff); + nRetVal = GetGeneralProperty(nStreamID, XN_LINK_PROP_ID_STREAM_SUPPORTED_INTERFACES, nBufferSize, supportedInterfacesBuff); + XN_IS_STATUS_OK_LOG_ERROR("Get supported interfaces", nRetVal); + nRetVal = xnLinkParseBitSetProp(XN_LINK_PROP_TYPE_GENERAL, supportedInterfacesBuff, nBufferSize, supportedInterfaces); + XN_IS_STATUS_OK_LOG_ERROR("Parse supported interfaces", nRetVal); + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::SetEmitterActive(XnBool bActive) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Turning emitter %s...", bActive ? "on" : "off"); + + nRetVal = SetIntProperty(XN_LINK_STREAM_ID_NONE, XN_LINK_PROP_ID_EMITTER_ACTIVE, XnUInt64(bActive)); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Emitter was turned %s", bActive ? "on" : "off"); + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::GetStreamFragLevel(XnUInt16 nStreamID, XnStreamFragLevel& streamFragLevel) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting stream %u fragmentation level...", nStreamID); + + XnUInt64 nTempStreamFragLevel = 0; + nRetVal = GetIntProperty(nStreamID, XN_LINK_PROP_ID_STREAM_FRAG_LEVEL, nTempStreamFragLevel); + XN_IS_STATUS_OK_LOG_ERROR("Get int property", nRetVal); + streamFragLevel = XnStreamFragLevel(nTempStreamFragLevel); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u fragmentation is %s", nStreamID, xnFragmentationFlagsToStr((XnLinkFragmentation)streamFragLevel)); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetMirror(XnUInt16 nStreamID, XnBool& bMirror) +{ + XnUInt64 nValue; + + xnLogVerbose(XN_MASK_LINK, "LINK: Checking if stream %u is mirrored...", nStreamID); + + XnStatus nRetVal = GetIntProperty(nStreamID, XN_LINK_PROP_ID_MIRROR, nValue); + XN_IS_STATUS_OK(nRetVal); + bMirror = (nValue == TRUE); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u is %smirrored", nStreamID, bMirror ? "" : "not "); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::SetMirror(XnUInt16 nStreamID, XnBool bMirror) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Turning stream %u mirror %s...", nStreamID, bMirror ? "on" : "off"); + + nRetVal = SetIntProperty(nStreamID, XN_LINK_PROP_ID_MIRROR, static_cast(bMirror)); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Stream %u mirror was turned %s", nStreamID, bMirror ? "on" : "off"); + + return (XN_STATUS_OK); +} + +XnStatus LinkControlEndpoint::FormatZone(XnUInt8 nZone) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Formatting zone...", nZone); + + XnUInt32 nResponseSize = m_nMaxResponseSize; + XnLinkFormatZoneParams formatZoneParams; + formatZoneParams.m_nZone = nZone; + nRetVal = ExecuteCommand(XN_LINK_MSG_FORMAT_ZONE, XN_LINK_STREAM_ID_NONE, &formatZoneParams, sizeof(formatZoneParams), m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute Format Zone command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: Zone %u formatted", nZone); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::StartUsbTest() +{ + XnUInt32 nResponseSize = m_nMaxResponseSize; + + xnLogVerbose(XN_MASK_LINK, "LINK: Starting USB test..."); + + XnStatus nRetVal = ExecuteCommand(XN_LINK_MSG_START_USB_TEST, 0, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute start usb test command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: USB Test started"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::StopUsbTest() +{ + XnUInt32 nResponseSize = m_nMaxResponseSize; + + xnLogVerbose(XN_MASK_LINK, "LINK: Stopping USB test..."); + + XnStatus nRetVal = ExecuteCommand(XN_LINK_MSG_STOP_USB_TEST, 0, NULL, 0, m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK_LOG_ERROR("Execute stop usb test command", nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: USB Test stopped"); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::GetBootStatus(XnBootStatus& bootStatus) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Getting boot status..."); + + XnLinkBootStatus* pBootStatus = + reinterpret_cast(m_pIncomingResponse); + XnUInt32 nResponseSize = m_nMaxResponseSize; + + nRetVal = GetGeneralProperty(XN_LINK_PROP_ID_NONE, XN_LINK_PROP_ID_BOOT_STATUS, nResponseSize, pBootStatus); + XN_IS_STATUS_OK_LOG_ERROR("Execute get boot status command", nRetVal); + + xnLinkParseBootStatus(bootStatus, *pBootStatus); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::OpenFWLogFile(XnUInt8 logID, XnUInt16 nLogStreamID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Enabling FW log file %u...", logID); + + XnLinkLogOpenCloseParams params; + params.m_nID = logID; + XnUInt32 nResponseSize = m_nMaxResponseSize; + + nRetVal = ExecuteCommand(XN_LINK_MSG_START_LOG_FILE, nLogStreamID, ¶ms, sizeof(params), m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: FW log file %u enabled", logID); + + return XN_STATUS_OK; +} + +XnStatus LinkControlEndpoint::CloseFWLogFile(XnUInt8 logID, XnUInt16 nLogStreamID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_LINK, "LINK: Disabling FW log file %u...", logID); + + XnLinkLogOpenCloseParams params; + params.m_nID = logID; + XnUInt32 nResponseSize = m_nMaxResponseSize; + + nRetVal = ExecuteCommand(XN_LINK_MSG_STOP_LOG_FILE, nLogStreamID, ¶ms, sizeof(params), m_pIncomingResponse, nResponseSize); + XN_IS_STATUS_OK(nRetVal); + + xnLogInfo(XN_MASK_LINK, "LINK: FW log file %u disabled", logID); + + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.h new file mode 100644 index 0000000..dee023d --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkControlEndpoint.h @@ -0,0 +1,166 @@ +#ifndef __XNLINKCONTROLENDPOINT_H__ +#define __XNLINKCONTROLENDPOINT_H__ + +#include "ISyncIOConnection.h" +#include "XnLinkMsgEncoder.h" +#include "XnLinkResponseMsgParser.h" +#include "XnArray.h" +#include "XnLinkDefs.h" +#include "XnLinkProtoLibDefs.h" +#include +#include +#include + +struct XnShiftToDepthConfig; + +namespace xn +{ + +class IConnectionFactory; +struct BaseStreamProps; + +class LinkControlEndpoint +{ +public: + static const XnUInt32 MUTEX_TIMEOUT; + + LinkControlEndpoint(); + virtual ~LinkControlEndpoint(); + + XnStatus Init(XnUInt32 nMaxOutMsgSize, IConnectionFactory* pConnectionFactory); + void Shutdown(); + + XnStatus Connect(); + void Disconnect(); + XnBool IsConnected() const; + +#ifdef DATA_ON_CONTROL + void SetStreamID(XnUInt16 nStreamID); +#endif + + //nResponseSize is max size on input, actual size on output + //pIsLast - optional. If provided, command will not automatically continue response, and the out value is whether this is the last packet. If NULL, all data is fetched automatically. + XnStatus ExecuteCommand(XnUInt16 nMsgType, XnUInt16 nStreamID, const void* pCmdData, XnUInt32 nCmdSize, void* pResponseData, XnUInt32& nResponseSize, XnBool* pIsLast = NULL); + XnStatus SendData(XnUInt16 nMsgType, const void* pCmdData, XnUInt32 nCmdSize, void* pResponseData, XnUInt32& nResponseSize); + XnUInt16 GetPacketID() const; + XN_MUTEX_HANDLE GetMutex() const; + XnBool IsMsgTypeSupported(XnUInt16 nMsgType); + XnBool IsPropertySupported(XnUInt16 nPropID); + + /* Specific commands */ + XnStatus GetFWVersion(XnLinkDetailedVersion& version); + XnStatus GetProtocolVersion(XnLeanVersion& version); + XnStatus GetHardwareVersion(XnUInt32& version); + XnStatus GetSerialNumber(XnChar* strSerialNumber, XnUInt32 nSize); + XnStatus GetComponentsVersions(xnl::Array& components); + XnStatus GetSupportedMsgTypes(xnl::Array& supportedMsgTypes); + XnStatus GetSupportedProperties(xnl::Array& supportedProperties); + XnStatus GetSupportedInterfaces(XnUInt16 nStreamID, xnl::BitSet& supportedInterfaces); + XnStatus GetBootStatus(XnBootStatus& bootStatus); + XnStatus UploadFile(const XnChar* strFileName, XnBool bOverrideFactorySettings); + XnStatus GetFileList(xnl::Array& files); + XnStatus DownloadFile(XnUInt16 zone, const XnChar* fwFileName, const XnChar* targetFile); + XnStatus StartStreaming(XnUInt16 nStreamID); + XnStatus StopStreaming(XnUInt16 nStreamID); + XnStatus SoftReset(); + XnStatus HardReset(); + XnStatus GetSupportedBistTests(xnl::Array& supportedTests); + XnStatus ExecuteBistTests(XnUInt32 nID, uint32_t& errorCode, uint32_t& extraDataSize, uint8_t* extraData); + XnStatus StartUsbTest(); + XnStatus StopUsbTest(); + XnStatus GetSupportedI2CDevices(xnl::Array& supporteddevices); + XnStatus WriteI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32 nValue, XnUInt32 nMask); + XnStatus ReadI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32& nValue); + XnStatus WriteAHB(XnUInt32 nAddress, XnUInt32 nValue, XnUInt8 nBitOffset, XnUInt8 nBitWidth); + XnStatus ReadAHB(XnUInt32 nAddress, XnUInt8 nBitOffset, XnUInt8 nBitWidth, XnUInt32& nValue); + XnStatus GetShiftToDepthConfig(XnUInt16 nStreamID, XnShiftToDepthConfig& shiftToDepthConfig); + XnStatus SetVideoMode(XnUInt16 nStreamID, const XnFwStreamVideoMode& videoMode); + XnStatus GetVideoMode(XnUInt16 nStreamID, XnFwStreamVideoMode& videoMode); + XnStatus GetSupportedVideoModes(XnUInt16 nStreamID, xnl::Array& supportedVideoModes); + XnStatus EnumerateStreams(xnl::Array& aStreamInfos); + XnStatus CreateInputStream(XnStreamType streamType, const XnChar* strCreationInfo, XnUInt16& nStreamID, XnUInt16& nEndpointID); + XnStatus DestroyInputStream(XnUInt16 nStreamID); + XnStatus SetCropping(XnUInt16 nStreamID, const OniCropping& cropping); + XnStatus GetCropping(XnUInt16 nStreamID, OniCropping& cropping); + XnStatus SetEmitterActive(XnBool bActive); + XnStatus GetSupportedLogFiles(xnl::Array& supportedFiles); + XnStatus OpenFWLogFile(XnUInt8 logID, XnUInt16 nLogStreamID); + XnStatus CloseFWLogFile(XnUInt8 logID, XnUInt16 nLogStreamID); + + //TODO: Implement Get emitter active + + XnStatus GetStreamFragLevel(XnUInt16 nStreamID, XnStreamFragLevel& streamFragLevel); + XnStatus GetMirror(XnUInt16 nStreamID, XnBool& bMirror); + XnStatus SetMirror(XnUInt16 nStreamID, XnBool bMirror); + + XnStatus BeginUpload(); + XnStatus EndUpload(); + XnStatus FormatZone(XnUInt8 nZone); + + /*DepthGenerator commands */ + XnStatus GetCameraIntrinsics(XnUInt16 nStreamID, XnLinkCameraIntrinsics& cameraIntrinsics); + +private: + static const XnUInt16 BASE_PACKET_ID; + static const XnUInt16 MAX_RESPONSE_NUM_PACKETS; //Max number of packets in response + static const XnChar MUTEX_NAME[]; + + XnStatus GetLogicalMaxPacketSize(XnUInt16& nMaxPacketSize); + + /** The fragmentation parameter in this function indicates the fragmentation of the whole data BLOCK, + not an individual packet. So if it's BEGIN, it means this block of data (one or more packets) begins + the message. If it's MIDDLE, it's the middle of the message, and if it's END, this block ends the + message.**/ + XnStatus ExecuteImpl(XnUInt16 nMsgType, + XnUInt16 nStreamID, + const void* pData, + XnUInt32 nSize, + XnLinkFragmentation fragmentation, + void* pResponseData, + XnUInt32& nResponseSize, + XnBool autoContinue, + XnBool& isLast); + + XnStatus ContinueResponseImpl(XnUInt16 originalMsgType, XnUInt16 streamID, void* pResponseData, XnUInt32& nResponseSize, XnBool& outLastPacket); + + XnStatus ValidateResponsePacket(const LinkPacketHeader* pResponsePacket, + XnUInt16 nExpectedMsgType, + XnUInt16 nExpectedStreamID, + XnUInt32 nBytesToRead); + + /* Properties */ + XnStatus SetIntProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt64 nValue); + XnStatus GetIntProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt64& nValue); + XnStatus SetRealProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnDouble dValue); + XnStatus GetRealProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnDouble& dValue); + XnStatus SetStringProperty(XnUInt16 nStreamID, XnLinkPropID propID, const XnChar* strValue); + XnStatus GetStringProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt32 nSize, XnChar* strValue); + XnStatus SetGeneralProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt32 nSize, const void* pSource); + //nSize is max size on input, actual size on output + XnStatus GetGeneralProperty(XnUInt16 nStreamID, XnLinkPropID propID, XnUInt32& nSize, void* pDest); + XnStatus GetBitSetProperty(XnUInt16 nStreamID, XnLinkPropID propID, xnl::BitSet& bitSet); + XnStatus SetProperty(XnUInt16 nStreamID, XnLinkPropType propType, XnLinkPropID propID, XnUInt32 nSize, const void* pSource); + XnStatus GetProperty(XnUInt16 nStreamID, XnLinkPropType propType, XnLinkPropID propID, XnUInt32& nSize, void* pDest); + + union + { + XnUInt8* m_pIncomingRawPacket; //Holds one packet, used for receiving from connection + LinkPacketHeader* m_pIncomingPacket; + }; + + XnUInt32 m_nMaxOutMsgSize; + ISyncIOConnection* m_pConnection; + LinkMsgEncoder m_msgEncoder; + LinkResponseMsgParser m_responseMsgParser; + XnUInt8* m_pIncomingResponse; //Holds complete parsed response (without link headers) + XnUInt32 m_nMaxResponseSize; + XnBool m_bInitialized; + XnBool m_bConnected; + XnUInt16 m_nPacketID; + XnUInt16 m_nMaxPacketSize; + XN_MUTEX_HANDLE m_hMutex; + xnl::Array m_supportedMsgTypes; //Array index is msgtype hi byte, position in bit set is msgtype lo byte. +}; + +} +#endif // __XNLINKCONTROLENDPOINT_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.cpp new file mode 100644 index 0000000..c7d2778 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.cpp @@ -0,0 +1,829 @@ +#include "XnLinkFrameInputStream.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkControlEndpoint.h" +#include +#include + +#include "XnLink12BitS2DParser.h" +#include "XnLink6BitParser.h" +#include "XnLinkPacked10BitParser.h" +#include "XnLinkUnpackedS2DParser.h" +#include "XnLink11BitS2DParser.h" +#include "XnLink16zParser.h" +#include "XnLink24zYuv422Parser.h" +#include "XnLinkYuv422ToRgb888Parser.h" + +#define _USE_MATH_DEFINES +#include + +#define XN_MASK_INPUT_STREAM "xnInputStream" + +namespace xn +{ + +LinkFrameInputStream::LinkFrameInputStream() +{ + m_bInitialized = FALSE; + m_defaultServices.setStream(this); + m_pServices = &m_defaultServices; + m_bStreaming = FALSE; + m_pCurrFrame = NULL; + m_nDumpFrameID = 0; + + m_frameIndex = 0; + + m_nBufferSize = 0; + m_hCriticalSection = NULL; + m_pDumpFile = NULL; + m_currentFrameCorrupt = FALSE; + xnOSCreateCriticalSection(&m_hCriticalSection); + + xnOSMemSet(&m_shiftToDepthConfig, 0, sizeof(m_shiftToDepthConfig)); + xnOSMemSet(&m_shiftToDepthTables, 0, sizeof(m_shiftToDepthTables)); +} + +LinkFrameInputStream::~LinkFrameInputStream() +{ + Shutdown(); + xnOSCloseCriticalSection(&m_hCriticalSection); +} + +XnStatus LinkFrameInputStream::Init(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection) +{ + XnStatus nRetVal = XN_STATUS_OK; + if (m_hCriticalSection == NULL) + { + xnLogError(XN_MASK_INPUT_STREAM, "Cannot initialize - critical section was not created successfully"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + xnl::AutoCSLocker csLock(m_hCriticalSection); + + if (m_bInitialized) + { + //We shutdown first so we can re-initialize. + Shutdown(); + } + + nRetVal = LinkInputStream::Init(pLinkControlEndpoint, streamType, nStreamID, pConnection); + XN_IS_STATUS_OK_LOG_ERROR("Init base link input stream", nRetVal); + //Now we have all the stream properties + m_nStreamID = nStreamID; + + nRetVal = pLinkControlEndpoint->GetSupportedVideoModes(nStreamID, m_supportedVideoModes); + XN_IS_STATUS_OK_LOG_ERROR("Get supported video modes", nRetVal); + + nRetVal = pLinkControlEndpoint->GetVideoMode(nStreamID, m_videoMode); + XN_IS_STATUS_OK_LOG_ERROR("Get video mode", nRetVal); + + if (IsInterfaceSupported(XN_LINK_INTERFACE_CROPPING)) + { + nRetVal = pLinkControlEndpoint->GetCropping(nStreamID, m_cropping); + XN_IS_STATUS_OK_LOG_ERROR("Get cropping", nRetVal); + } + + nRetVal = UpdateCameraIntrinsics(); + XN_IS_STATUS_OK_LOG_ERROR("Update Camera Intrinsics", nRetVal); + + // if needed, build shift-to-depth tables + if (streamType == XN_LINK_STREAM_TYPE_SHIFTS) + { + nRetVal = pLinkControlEndpoint->GetShiftToDepthConfig(nStreamID, m_shiftToDepthConfig); + XN_IS_STATUS_OK_LOG_ERROR("Get S2D config", nRetVal); + + // construct tables + nRetVal = XnShiftToDepthInit(&m_shiftToDepthTables, &m_shiftToDepthConfig); + XN_IS_STATUS_OK_LOG_ERROR("Init shift to depth tables", nRetVal); + } + + nRetVal = xnLinkGetStreamDumpName(m_nStreamID, m_strDumpName, sizeof(m_strDumpName)); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning("Failed to get stream dump name: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } + + if (m_hCriticalSection == NULL) + { + nRetVal = xnOSCreateCriticalSection(&m_hCriticalSection); + XN_IS_STATUS_OK_LOG_ERROR("Create critical section", nRetVal); + } + + m_nDumpFrameID = 1; + m_bInitialized = TRUE; + + return XN_STATUS_OK; +} + +void LinkFrameInputStream::Reset() +{ + xnOSMemSet(&m_videoMode, 0, sizeof(m_videoMode)); + xnOSMemSet(&m_cropping, 0, sizeof(m_cropping)); + LinkInputStream::Reset(); +} + +XnBool LinkFrameInputStream::IsInitialized() const +{ + return m_bInitialized; +} + +void LinkFrameInputStream::Shutdown() +{ + if (!m_bInitialized) + return; + + xnOSEnterCriticalSection(&m_hCriticalSection); + Stop(); + + if (m_pCurrFrame != NULL) + { + m_pServices->releaseFrame(m_pCurrFrame); + m_pCurrFrame = NULL; + } + + XnShiftToDepthFree(&m_shiftToDepthTables); + + xnDumpFileClose(m_pDumpFile); + LinkInputStream::Shutdown(); + m_bInitialized = FALSE; + xnOSLeaveCriticalSection(&m_hCriticalSection); +} + +XnStatus LinkFrameInputStream::HandlePacket(const LinkPacketHeader& origHeader, const XnUInt8* pData, XnBool& bPacketLoss) +{ + XnStatus nRetVal = XN_STATUS_OK; + xnl::AutoCSLocker csLock(m_hCriticalSection); + if (!m_bInitialized) + { + return XN_STATUS_NOT_INIT; + } + + LinkPacketHeader header = origHeader; + + if (header.GetFragmentationFlags() & XN_LINK_FRAG_BEGIN) + { + bPacketLoss = FALSE; + + xnDumpFileClose(m_pDumpFile); //In case we didn't get an END packet before + m_pDumpFile = xnDumpFileOpen(m_strDumpName, "%s.%05u.raw", m_strDumpName, m_nDumpFrameID++); + m_currentFrameCorrupt = FALSE; + + // acquire a frame + if (m_pCurrFrame == NULL) + { + m_pCurrFrame = m_pServices->acquireFrame(); + if (m_pCurrFrame == NULL) + { + xnLogError(XN_MASK_LINK, "Failed to acquire frame. Stream can't function!"); + return XN_STATUS_ALLOC_FAILED; + } + } + + // take timestamp + if (header.GetDataSize() < sizeof(XnUInt64)) + { + m_currentFrameCorrupt = TRUE; + xnLogWarning(XN_MASK_LINK, "Got a BEGIN packet with no timestamp!"); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_MISSING_TIMESTAMP; + } + m_pCurrFrame->timestamp = *(XnUInt64*)pData; + pData += sizeof(XnUInt64); + header.SetSize(header.GetSize() - sizeof(XnUInt64)); + + // TEMP: inject the host's timestamp. Firmware can't produce timestamps yet + XnUInt64 nTimestamp; + nRetVal = xnOSGetHighResTimeStamp(&nTimestamp); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "Failed to get timestamp from os: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } + m_pCurrFrame->timestamp = nTimestamp; + + // begin parsing frame + nRetVal = m_pLinkMsgParser->BeginParsing(m_pCurrFrame->data, m_nBufferSize); + XN_IS_STATUS_OK_LOG_ERROR("Begin parsing link frame msg", nRetVal); + } + else if (bPacketLoss) + { + m_currentFrameCorrupt = TRUE; + } + + if (!m_currentFrameCorrupt) + { + XnUInt32 nPrevSize = m_pLinkMsgParser->GetParsedSize(); + nRetVal = m_pLinkMsgParser->ParsePacket(header, pData); + if (nRetVal != XN_STATUS_OK) + { + m_currentFrameCorrupt = TRUE; + if (nRetVal != XN_STATUS_OK) + { + return nRetVal; + } + } + + //Write new data to dump (if it's on) + xnDumpFileWriteBuffer(m_pDumpFile, + reinterpret_cast(m_pLinkMsgParser->GetParsedData()) + nPrevSize, + m_pLinkMsgParser->GetParsedSize() - nPrevSize); + } + + if (header.GetFragmentationFlags() & XN_LINK_FRAG_END) + { + /* Yay, we now have a full message. */ + xnDumpFileClose(m_pDumpFile); + + if (m_pLinkMsgParser->GetParsedSize() != CalcExpectedSize()) + { + m_currentFrameCorrupt = TRUE; + xnLogWarning(XN_MASK_LINK, "Received bad frame. Expected Size: %u, Actual Size: %u", CalcExpectedSize(), m_pLinkMsgParser->GetParsedSize()); + } + + if (!m_currentFrameCorrupt) + { + //Save actual size of data in working buffer info + m_pCurrFrame->dataSize = m_pLinkMsgParser->GetParsedSize(); + m_pCurrFrame->frameIndex = m_frameIndex++; + + m_pCurrFrame->frameIndex = m_frameIndex++; + m_pCurrFrame->croppingEnabled = m_cropping.enabled; + if (m_cropping.enabled) + { + m_pCurrFrame->width = m_cropping.width; + m_pCurrFrame->height = m_cropping.height; + m_pCurrFrame->cropOriginX = m_cropping.originX; + m_pCurrFrame->cropOriginY = m_cropping.originY; + } else { + m_pCurrFrame->width = m_videoMode.m_nXRes; + m_pCurrFrame->height = m_videoMode.m_nYRes; + m_pCurrFrame->cropOriginX = 0; + m_pCurrFrame->cropOriginY = 0; + } + m_pCurrFrame->stride = m_pCurrFrame->width * GetOutputBytesPerPixel(); + m_pCurrFrame->videoMode.fps = m_videoMode.m_nFPS; + m_pCurrFrame->videoMode.pixelFormat = m_outputFormat; + m_pCurrFrame->videoMode.resolutionX = m_videoMode.m_nXRes; + m_pCurrFrame->videoMode.resolutionY = m_videoMode.m_nYRes; + + switch (m_streamType) + { + case XN_LINK_STREAM_TYPE_SHIFTS: m_pCurrFrame->sensorType = ONI_SENSOR_DEPTH; break; + case XN_LINK_STREAM_TYPE_COLOR: m_pCurrFrame->sensorType = ONI_SENSOR_COLOR; break; + case XN_LINK_STREAM_TYPE_IR: m_pCurrFrame->sensorType = ONI_SENSOR_IR; break; + } + + NewFrameEventArgs args; + args.pFrame = m_pCurrFrame; + nRetVal = m_newFrameEvent.Raise(args); + m_pServices->releaseFrame(m_pCurrFrame); + m_pCurrFrame = NULL; + XN_IS_STATUS_OK_LOG_ERROR("Raise new frame event", nRetVal); + } + } + + return XN_STATUS_OK; +} + +XnStatus LinkFrameInputStream::StartImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_bStreaming) + { + //Already streaming + return XN_STATUS_OK; + } + + //Allocate buffers + m_nBufferSize = CalcBufferSize(); + if (m_nBufferSize == 0) + { + xnLogError(XN_MASK_LINK, "Failed to calculate buffer size for stream of type %u", m_streamType); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + xnLogVerbose(XN_MASK_LINK, "Stream %u calculated buffer size: %u", m_nStreamID, m_nBufferSize); + + //Prepare parser + m_pLinkMsgParser = CreateLinkMsgParser(); + XN_VALIDATE_ALLOC_PTR(m_pLinkMsgParser); + nRetVal = m_pLinkMsgParser->Init(); + XN_IS_STATUS_OK_LOG_ERROR("Init link msg parser", nRetVal); + //TODO: Delete LinkMsgParser and buffers on each of these errors + + //We must set the streaming flag first cuz the data handler checks it + m_bStreaming = TRUE; + //Connect to input connection + nRetVal = m_pConnection->Connect(); + if (nRetVal != XN_STATUS_OK) + { + m_bStreaming = FALSE; + xnLogError(XN_MASK_LINK, "Failed to connect stream's input connection: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return nRetVal; + } + + //Now send command to device + nRetVal = m_pLinkControlEndpoint->StartStreaming(m_nStreamID); + XN_IS_STATUS_OK_LOG_ERROR("Connect stream's input connection", nRetVal); + if (nRetVal != XN_STATUS_OK) + { + m_bStreaming = FALSE; + xnLogError(XN_MASK_LINK, "Failed to start streaming: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return nRetVal; + } + + return XN_STATUS_OK; +} + +XnStatus LinkFrameInputStream::StopImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + if (!m_bStreaming) + { + return XN_STATUS_OK; + } + + m_pLinkControlEndpoint->StopStreaming(m_nStreamID); + XN_IS_STATUS_OK_LOG_ERROR("Stop streaming", nRetVal); + m_pConnection->Disconnect(); + + if (m_pLinkMsgParser != NULL) + { + m_pLinkMsgParser->Shutdown(); + XN_DELETE(m_pLinkMsgParser); + m_pLinkMsgParser = NULL; + } + + //Free curr buffer if we still hold it + if (m_pCurrFrame != NULL) + { + m_pServices->releaseFrame(m_pCurrFrame); + m_pCurrFrame = NULL; + } + + m_bStreaming = FALSE; + + return XN_STATUS_OK; +} + +void LinkFrameInputStream::SetDumpName(const XnChar* /*strDumpName*/) +{ + //Not implemented for frame input stream + XN_ASSERT(FALSE); +} + +void LinkFrameInputStream::SetDumpOn(XnBool bDumpOn) +{ + XnStatus nRetVal = XN_STATUS_OK; + (void)nRetVal; + + nRetVal = xnDumpSetMaskState(m_strDumpName, bDumpOn); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_INPUT_STREAM, "Failed to set dump state: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } +} + +void LinkFrameInputStream::Swap(XnUInt32& nVal1, XnUInt32& nVal2) +{ + XnUInt32 nTemp = nVal1; + nVal1 = nVal2; + nVal2 = nTemp; +} + +XnUInt32 LinkFrameInputStream::GetOutputBytesPerPixel() const +{ + if (m_outputFormat == XN_FORMAT_PASS_THROUGH_RAW || + m_outputFormat == XN_FORMAT_PASS_THROUGH_UNPACK ) + { + return xnLinkGetPixelSizeByStreamType(XnLinkStreamType(m_streamType)); + } + switch (m_outputFormat) + { + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + return sizeof(OniDepthPixel); + case ONI_PIXEL_FORMAT_YUV422: + return sizeof(OniYUV422DoublePixel)/2; + case ONI_PIXEL_FORMAT_RGB888: + return sizeof(OniRGB888Pixel); + case ONI_PIXEL_FORMAT_GRAY16: + return sizeof(OniGrayscale16Pixel); + default: + XN_ASSERT(FALSE); + xnLogError(XN_MASK_LINK, "Unknown output format!"); + return 0; + } +} + +XnUInt32 LinkFrameInputStream::CalcBufferSize() const +{ + XnUInt32 nPixelSize = 0; + + if (IsInterfaceSupported(XN_LINK_INTERFACE_MAP_GENERATOR)) + { + //Stream is map based - calculate size according to current resolution. + nPixelSize = GetOutputBytesPerPixel(); + if (nPixelSize == 0) + { + //This means we got a bad stream type + return 0; + } + + //64 bits for timestamp, then space for data according to resolution and pixel width + return (nPixelSize * m_videoMode.m_nXRes * m_videoMode.m_nYRes); + } + else + { + // TEMP: replace this with a value taken from firmware + return (10 * 1024); + } +} + +XnUInt32 LinkFrameInputStream::CalcExpectedSize() const +{ + XnUInt32 nPixelSize = 0; + + if (IsInterfaceSupported(XN_LINK_INTERFACE_MAP_GENERATOR)) + { + //Stream is map based - calculate size according to current resolution. + nPixelSize = GetOutputBytesPerPixel(); + if (nPixelSize == 0) + { + //This means we got a bad stream type + return 0; + } + + if (m_cropping.enabled) + { + return (nPixelSize * m_cropping.width * m_cropping.height); + } + else + { + return (nPixelSize * m_videoMode.m_nXRes * m_videoMode.m_nYRes); + } + } + else + { + return (0); + } +} + +XnBool LinkFrameInputStream::IsOutputFormatSupported(OniPixelFormat format) const +{ + if (format == XN_FORMAT_PASS_THROUGH_RAW || + format == XN_FORMAT_PASS_THROUGH_UNPACK ) + { + return TRUE; + } + switch (format) + { + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + return (m_streamType == XN_LINK_STREAM_TYPE_SHIFTS); + case ONI_PIXEL_FORMAT_YUV422: + return (m_streamType == XN_LINK_STREAM_TYPE_COLOR) && (m_videoMode.m_nPixelFormat == XN_FW_PIXEL_FORMAT_YUV422); + case ONI_PIXEL_FORMAT_RGB888: + return (m_streamType == XN_LINK_STREAM_TYPE_COLOR) && (m_videoMode.m_nPixelFormat == XN_FW_PIXEL_FORMAT_BAYER8); + case ONI_PIXEL_FORMAT_GRAY16: + return (m_streamType == XN_LINK_STREAM_TYPE_COLOR) && (m_videoMode.m_nPixelFormat == XN_FW_PIXEL_FORMAT_GRAYSCALE16); + default: + return LinkInputStream::IsOutputFormatSupported(format); + } +} + +const xnl::Array& LinkFrameInputStream::GetSupportedVideoModes() const +{ + return m_supportedVideoModes; +} + +const XnFwStreamVideoMode& LinkFrameInputStream::GetVideoMode() const +{ + return m_videoMode; +} + +XnStatus LinkFrameInputStream::SetVideoMode(const XnFwStreamVideoMode& videoMode) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnBool bModeSupported = FALSE; + + XnChar strVideoMode[100]; + xnLinkVideoModeToString(videoMode, strVideoMode, sizeof(strVideoMode)); + + xnLogVerbose(XN_MASK_LINK, "Stream %u - Setting video mode %s...", m_nStreamID, strVideoMode); + + for (XnUInt32 i = 0; i < m_supportedVideoModes.GetSize() && !bModeSupported; i++) + { + if (xnOSMemCmp(&videoMode, &m_supportedVideoModes[i], sizeof(videoMode)) == 0) + { + //Found our supported mode + bModeSupported = TRUE; + } + } + + if (!bModeSupported) + { + xnLogError(XN_MASK_LINK, "Tried to set unsupported mode: %s", strVideoMode); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + nRetVal = m_pLinkControlEndpoint->SetVideoMode(m_nStreamID, videoMode); + XN_IS_STATUS_OK_LOG_ERROR("Set map output mode", nRetVal); + + m_videoMode = videoMode; + + nRetVal = UpdateCameraIntrinsics(); + XN_IS_STATUS_OK_LOG_ERROR("Update Camera Intrinsics", nRetVal); + + // if needed, build shift-to-depth tables + if (m_streamType == XN_LINK_STREAM_TYPE_SHIFTS) + { + nRetVal = m_pLinkControlEndpoint->GetShiftToDepthConfig(m_nStreamID, m_shiftToDepthConfig); + XN_IS_STATUS_OK(nRetVal); + + // construct tables + nRetVal = XnShiftToDepthUpdate(&m_shiftToDepthTables, &m_shiftToDepthConfig); + XN_IS_STATUS_OK_LOG_ERROR("update shift to depth tables", nRetVal); + } + + return XN_STATUS_OK; +} + +const XnShiftToDepthConfig& LinkFrameInputStream::GetShiftToDepthConfig() const +{ + return m_shiftToDepthConfig; +} + +XnStatus LinkFrameInputStream::GetShiftToDepthTables(const XnShiftToDepthTables*& pTables) const +{ + if (!m_shiftToDepthTables.bIsInitialized) + { + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + pTables = &m_shiftToDepthTables; + + return XN_STATUS_OK; +} + +XnStatus LinkFrameInputStream::SetDepthScale(XnDouble dDepthScale) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnDouble dPrevScale = m_shiftToDepthConfig.dDepthScale; + + if (dDepthScale != dPrevScale) + { + XnDouble dNewMaxCutOff = m_shiftToDepthConfig.nDepthMaxCutOff / dPrevScale * dDepthScale; + if (dNewMaxCutOff > m_shiftToDepthConfig.nDeviceMaxDepthValue) + { + xnLogError(XN_MASK_LINK, "Can't set depth scale to %f: this will create a cut off larger than max depth (%u > %u)", + dDepthScale, (XnUInt32)dNewMaxCutOff, m_shiftToDepthConfig.nDeviceMaxDepthValue); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + m_shiftToDepthConfig.dDepthScale = dDepthScale; + m_shiftToDepthConfig.nDepthMaxCutOff = (OniDepthPixel)(m_shiftToDepthConfig.nDepthMaxCutOff / dPrevScale * dDepthScale); + m_shiftToDepthConfig.nDepthMinCutOff = (OniDepthPixel)(m_shiftToDepthConfig.nDepthMinCutOff / dPrevScale * dDepthScale); + + nRetVal = XnShiftToDepthUpdate(&m_shiftToDepthTables, &m_shiftToDepthConfig); + XN_IS_STATUS_OK(nRetVal); + } + + return (XN_STATUS_OK); +} + +const OniCropping& LinkFrameInputStream::GetCropping() const +{ + return m_cropping; +} + +XnStatus LinkFrameInputStream::SetCropping(OniCropping cropping) +{ + // validate + if (cropping.enabled) + { + if ((XnUInt32(cropping.originX + cropping.width) > m_videoMode.m_nXRes) || + (XnUInt32(cropping.originY + cropping.height) > m_videoMode.m_nYRes)) + { + xnLogWarning(XN_MASK_LINK, "cropping window is out of full resolution"); + return XN_STATUS_BAD_PARAM; + } + } + + XnStatus nRetVal = m_pLinkControlEndpoint->SetCropping(m_nStreamID, cropping); + XN_IS_STATUS_OK_LOG_ERROR("Set cropping", nRetVal); + m_cropping = cropping; + return XN_STATUS_OK; +} + +LinkMsgParser* LinkFrameInputStream::CreateLinkMsgParser() +{ + OniPixelFormat outputFormat = m_outputFormat; + XnFwPixelFormat pixelFormat = m_videoMode.m_nPixelFormat; + XnFwCompressionType compression = m_videoMode.m_nCompression; + + // TODO: validate this is a depth stream if format requires S2D + + if (outputFormat == XN_FORMAT_PASS_THROUGH_RAW) + { + return XN_NEW(LinkMsgParser); + } else if (outputFormat == XN_FORMAT_PASS_THROUGH_UNPACK) + { + switch (compression) + { + case XN_FW_COMPRESSION_NONE: + return XN_NEW(LinkMsgParser); + case XN_FW_COMPRESSION_6_BIT_PACKED: + return XN_NEW(Link6BitParser); + case XN_FW_COMPRESSION_10_BIT_PACKED: + return XN_NEW(LinkPacked10BitParser); + case XN_FW_COMPRESSION_16Z: + return XN_NEW(Link16zParser, m_shiftToDepthTables); + case XN_FW_COMPRESSION_24Z: + return XN_NEW(Link24zYuv422Parser, m_videoMode.m_nXRes, m_videoMode.m_nYRes, FALSE); + default: + xnLogError(XN_MASK_LINK, "Unknown compression for pass-through: %d", compression); + XN_ASSERT(FALSE); + return NULL; + } + } + switch (outputFormat) + { + case ONI_PIXEL_FORMAT_DEPTH_1_MM: + { + if (pixelFormat != XN_FW_PIXEL_FORMAT_SHIFTS_9_3) + { + xnLogError(XN_MASK_LINK, "Cannot convert from pixel format %d to depth!", pixelFormat); + XN_ASSERT(pixelFormat == XN_LINK_PIXEL_FORMAT_SHIFTS_9_3); + return NULL; + } + + switch (compression) + { + case XN_FW_COMPRESSION_NONE: + return XN_NEW(LinkUnpackedS2DParser, m_shiftToDepthTables); + case XN_FW_COMPRESSION_11_BIT_PACKED: + return XN_NEW(Link11BitS2DParser, m_shiftToDepthTables); + case XN_FW_COMPRESSION_12_BIT_PACKED: + return XN_NEW(Link12BitS2DParser, m_shiftToDepthTables); + case XN_FW_COMPRESSION_16Z: + return XN_NEW(Link16zParser, m_shiftToDepthTables); + default: + xnLogError(XN_MASK_LINK, "Unknown compression for shifts: %d", compression); + XN_ASSERT(FALSE); + return NULL; + } + } + case ONI_PIXEL_FORMAT_YUV422: + { + if (pixelFormat != XN_FW_PIXEL_FORMAT_YUV422) + { + xnLogError(XN_MASK_LINK, "Cannot convert from pixel format %d to YUV422!", pixelFormat); + XN_ASSERT(pixelFormat == XN_LINK_PIXEL_FORMAT_YUV422); + return NULL; + } + + switch (compression) + { + case XN_FW_COMPRESSION_NONE: + return XN_NEW(LinkMsgParser); + case XN_FW_COMPRESSION_24Z: + return XN_NEW(Link24zYuv422Parser, m_videoMode.m_nXRes, m_videoMode.m_nYRes, FALSE); + default: + xnLogError(XN_MASK_LINK, "Unknown compression YUV422: %d", compression); + XN_ASSERT(FALSE); + return NULL; + } + } + case ONI_PIXEL_FORMAT_RGB888: + { + if (pixelFormat == XN_FW_PIXEL_FORMAT_YUV422) + { + switch (compression) + { + case XN_FW_COMPRESSION_NONE: + return XN_NEW(LinkYuv422ToRgb888Parser); + case XN_FW_COMPRESSION_24Z: + return XN_NEW(Link24zYuv422Parser, m_videoMode.m_nXRes, m_videoMode.m_nYRes, TRUE); + default: + xnLogError(XN_MASK_LINK, "Unknown compression YUV422: %d", compression); + XN_ASSERT(FALSE); + return NULL; + } + } + else if (pixelFormat == XN_FW_PIXEL_FORMAT_BAYER8) + { + xnLogError(XN_MASK_LINK, "Bayer to RGB888 conversion is not supported yet"); + XN_ASSERT(FALSE); + return NULL; + } + } + case ONI_PIXEL_FORMAT_GRAY16: + switch (compression) + { + case XN_FW_COMPRESSION_NONE: + return XN_NEW(LinkMsgParser); + case XN_FW_COMPRESSION_10_BIT_PACKED: + return XN_NEW(LinkPacked10BitParser); + default: + xnLogError(XN_MASK_LINK, "Unknown compression for grey16: %d", compression); + XN_ASSERT(FALSE); + return NULL; + } + default: + xnLogError(XN_MASK_LINK, "Unknown output format: %d", outputFormat); + XN_ASSERT(FALSE); + return NULL; + } +} + +XnStatus LinkFrameInputStream::UpdateCameraIntrinsics() +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_pLinkControlEndpoint->GetCameraIntrinsics(m_nStreamID, m_cameraIntrinsics); + XN_IS_STATUS_OK_LOG_ERROR("Get Camera Intrinsics", nRetVal); + + m_fHFOV = (XnFloat)(2 * atan(m_videoMode.m_nXRes / 2. / m_cameraIntrinsics.m_fEffectiveFocalLengthInPixels)); + m_fVFOV = (XnFloat)(2 * atan(m_videoMode.m_nYRes / 2. / m_cameraIntrinsics.m_fEffectiveFocalLengthInPixels)); + + xnLogVerbose(XN_MASK_LINK, "Stream %u intrinsics - EFL: %.2f, Optic Center: (%u,%u), Field-of-View: %.1fx%.1f", + m_nStreamID, + m_cameraIntrinsics.m_fEffectiveFocalLengthInPixels, + m_cameraIntrinsics.m_nOpticalCenterX, + m_cameraIntrinsics.m_nOpticalCenterY, + m_fHFOV*180/M_PI, + m_fVFOV*180/M_PI); + + return (XN_STATUS_OK); +} + + +LinkFrameInputStream::DefaultStreamServices::DefaultStreamServices() +{ + OniStreamServices::getDefaultRequiredFrameSize = getDefaultRequiredFrameSizeCallback; + OniStreamServices::acquireFrame = acquireFrameCallback; + OniStreamServices::addFrameRef = addFrameRefCallback; + OniStreamServices::releaseFrame = releaseFrameCallback; +} + +void LinkFrameInputStream::DefaultStreamServices::setStream(LinkFrameInputStream* pStream) +{ + OniStreamServices::streamServices = pStream; +} + +int ONI_CALLBACK_TYPE LinkFrameInputStream::DefaultStreamServices::getDefaultRequiredFrameSizeCallback(void* streamServices) +{ + LinkFrameInputStream* pThis = (LinkFrameInputStream*)streamServices; + return pThis->GetRequiredFrameSize(); +} + +OniFrame* ONI_CALLBACK_TYPE LinkFrameInputStream::DefaultStreamServices::acquireFrameCallback(void* streamServices) +{ + LinkFrameInputStream* pThis = (LinkFrameInputStream*)streamServices; + LinkOniFrame* pFrame = XN_NEW(LinkOniFrame); + if (pFrame == NULL) + { + return NULL; + } + + pFrame->refCount = 1; + pFrame->dataSize = pThis->GetRequiredFrameSize(); + pFrame->data = xnOSMallocAligned(pFrame->dataSize, XN_DEFAULT_MEM_ALIGN); + if (pFrame->data == NULL) + { + XN_DELETE(pFrame); + return NULL; + } + + return pFrame; +} + +void ONI_CALLBACK_TYPE LinkFrameInputStream::DefaultStreamServices::addFrameRefCallback(void* /*streamServices*/, OniFrame* pFrame) +{ + LinkOniFrame* pLinkFrame = (LinkOniFrame*)pFrame; + ++pLinkFrame->refCount; +} + +void ONI_CALLBACK_TYPE LinkFrameInputStream::DefaultStreamServices::releaseFrameCallback(void* /*streamServices*/, OniFrame* pFrame) +{ + LinkOniFrame* pLinkFrame = (LinkOniFrame*)pFrame; + if (--pLinkFrame->refCount == 0) + { + xnOSFreeAligned(pLinkFrame->data); + XN_DELETE(pLinkFrame); + } +} + +} + diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.h new file mode 100644 index 0000000..d6068a8 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkFrameInputStream.h @@ -0,0 +1,137 @@ +#ifndef __XNLINKFRAMEINPUTSTREAM_H__ +#define __XNLINKFRAMEINPUTSTREAM_H__ + +#include "XnLinkMsgParser.h" +#include "XnLinkInputStream.h" +#include +#include +#include + +#include + +struct XnDumpFile; + +namespace xn +{ + +class LinkControlEndpoint; + +typedef struct NewFrameEventArgs +{ + OniFrame* pFrame; +} NewFrameEventArgs; + +typedef xnl::Event NewFrameEvent; + +class LinkFrameInputStream : public LinkInputStream +{ +public: + LinkFrameInputStream(); + virtual ~LinkFrameInputStream(); + virtual XnStatus Init(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection); + + virtual void SetStreamServices(oni::driver::StreamServices* pServices) { m_pServices = pServices; } + + XnUInt32 GetRequiredFrameSize() const { return CalcBufferSize(); } + + virtual void Reset(); + + virtual XnBool IsInitialized() const; + virtual void Shutdown(); + virtual XnStatus HandlePacket(const LinkPacketHeader& header, const XnUInt8* pData, XnBool& bPacketLoss); + + virtual void SetDumpName(const XnChar* strDumpName); + virtual void SetDumpOn(XnBool bDumpOn); + + virtual XnStreamFragLevel GetStreamFragLevel() const { return XN_LINK_STREAM_FRAG_LEVEL_FRAMES; } + + typedef void (XN_CALLBACK_TYPE* NewFrameEventHandler)(const NewFrameEventArgs& args, void* pCookie); + NewFrameEvent::EventInterface& GetNewFrameEvent() { return m_newFrameEvent; } + + virtual XnBool IsOutputFormatSupported(OniPixelFormat format) const; + + virtual const xnl::Array& GetSupportedVideoModes() const; + virtual const XnFwStreamVideoMode& GetVideoMode() const; + virtual XnStatus SetVideoMode(const XnFwStreamVideoMode& videoMode); + + virtual const XnShiftToDepthConfig& GetShiftToDepthConfig() const; + virtual XnStatus GetShiftToDepthTables(const XnShiftToDepthTables*& pTables) const; + virtual XnStatus SetDepthScale(XnDouble dDepthScale); + + virtual const XnLinkCameraIntrinsics& GetCameraIntrinsics() const { return m_cameraIntrinsics; } + + virtual const OniCropping& GetCropping() const; + virtual XnStatus SetCropping(OniCropping cropping); + + virtual void GetFieldOfView(XnFloat* pHFOV, XnFloat* pVFOV) const { if (pHFOV) *pHFOV = m_fHFOV; if (pVFOV) *pVFOV = m_fVFOV; } + +protected: + virtual XnStatus StartImpl(); + virtual XnStatus StopImpl(); + + virtual LinkMsgParser* CreateLinkMsgParser(); + +private: + class DefaultStreamServices : public oni::driver::StreamServices + { + public: + DefaultStreamServices(); + void setStream(LinkFrameInputStream* pStream); + static int ONI_CALLBACK_TYPE getDefaultRequiredFrameSizeCallback(void* streamServices); + static OniFrame* ONI_CALLBACK_TYPE acquireFrameCallback(void* streamServices); + static void ONI_CALLBACK_TYPE releaseFrameCallback(void* streamServices, OniFrame* pFrame); + static void ONI_CALLBACK_TYPE addFrameRefCallback(void* streamServices, OniFrame* pFrame); + }; + + struct LinkOniFrame : public OniFrame + { + int refCount; + }; + + static void Swap(XnUInt32& nVal1, XnUInt32& nVal2); + XnUInt32 GetOutputBytesPerPixel() const; + virtual XnUInt32 CalcBufferSize() const; + XnUInt32 CalcExpectedSize() const; + XnStatus UpdateCameraIntrinsics(); + + DefaultStreamServices m_defaultServices; + + oni::driver::StreamServices* m_pServices; + + volatile XnBool m_bInitialized; + + NewFrameEvent m_newFrameEvent; + OniFrame* m_pCurrFrame; + + XnBool m_currentFrameCorrupt; + mutable XN_CRITICAL_SECTION_HANDLE m_hCriticalSection; //Protects buffers info + + XnUInt32 m_nBufferSize; + LinkMsgParser* m_pLinkMsgParser; + + XnDumpFile* m_pDumpFile; + XnChar m_strDumpName[XN_FILE_MAX_PATH]; + XnUInt32 m_nDumpFrameID; + + xnl::Array m_supportedVideoModes; + XnFwStreamVideoMode m_videoMode; + + int m_frameIndex; + OniCropping m_cropping; + + XnShiftToDepthConfig m_shiftToDepthConfig; + XnShiftToDepthTables m_shiftToDepthTables; + + XnLinkCameraIntrinsics m_cameraIntrinsics; + + // Field of View + XnFloat m_fHFOV; + XnFloat m_fVFOV; +}; + +} + +#endif // __XNLINKFRAMEINPUTSTREAM_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.cpp new file mode 100644 index 0000000..f472dd3 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.cpp @@ -0,0 +1,161 @@ +#include "XnLinkInputDataEndpoint.h" +#include "IConnectionFactory.h" +#include "XnLinkInputStreamsMgr.h" +#include +#include +#include +#include + +#define XN_MASK_LINK "xnLink" + +namespace xn +{ + +LinkInputDataEndpoint::LinkInputDataEndpoint() +{ + m_pConnection = NULL; + m_pConnectionFactory = NULL; + m_bInitialized = FALSE; + m_nConnected = 0; + m_pNotifications = NULL; + m_nEndpointID = 0; + m_pDumpFile = NULL; + m_pLinkInputStreamsMgr = NULL; + m_hCriticalSection = NULL; +} + +LinkInputDataEndpoint::~LinkInputDataEndpoint() +{ + Shutdown(); +} + +XnStatus LinkInputDataEndpoint::Init(XnUInt16 nEndpointID, + IConnectionFactory* pConnectionFactory, + LinkInputStreamsMgr* pLinkInputStreamsMgr, + ILinkDataEndpointNotifications* pNotifications) +{ + XN_VALIDATE_INPUT_PTR(pConnectionFactory); + XN_VALIDATE_INPUT_PTR(pLinkInputStreamsMgr); + XN_VALIDATE_INPUT_PTR(pNotifications); + + XnStatus nRetVal = XN_STATUS_OK; + if (!m_bInitialized) + { + m_pConnectionFactory = pConnectionFactory; + m_nEndpointID = nEndpointID; + m_pNotifications = pNotifications; + m_pLinkInputStreamsMgr = pLinkInputStreamsMgr; + + nRetVal = xnOSCreateCriticalSection(&m_hCriticalSection); + XN_IS_STATUS_OK_LOG_ERROR("Create critical section", nRetVal); + + //We are initialized :) + m_bInitialized = TRUE; + } + + return XN_STATUS_OK; +} + +XnBool LinkInputDataEndpoint::IsInitialized() const +{ + return m_bInitialized; +} + +void LinkInputDataEndpoint::Shutdown() +{ + Disconnect(); + XN_DELETE(m_pConnection); + m_pConnection = NULL; + xnOSCloseCriticalSection(&m_hCriticalSection); + m_bInitialized = FALSE; +} + +XnStatus LinkInputDataEndpoint::Connect() +{ + XnStatus nRetVal = XN_STATUS_OK; + XnChar strDumpName[XN_FILE_MAX_PATH] = ""; + xnl::AutoCSLocker lock(m_hCriticalSection); + + if (!m_bInitialized) + { + XN_LOG_ERROR_RETURN(XN_STATUS_NOT_INIT, XN_MASK_LINK, "Not initialized"); + } + + if (m_nConnected == 0) + { + if (m_pConnection == NULL) + { + //Create input data connection + nRetVal = m_pConnectionFactory->CreateInputDataConnection(m_nEndpointID, m_pConnection); + XN_IS_STATUS_OK_LOG_ERROR("Create input data connection", nRetVal); + xnLogVerbose(XN_MASK_LINK, "Link input data endpoint %u max packet size is %u bytes", m_nEndpointID, m_pConnection->GetMaxPacketSize()); + } + + //Tell our async connection object to send incoming data to this object + nRetVal = m_pConnection->SetDataDestination(this); + XN_IS_STATUS_OK_LOG_ERROR("Set input data connection data destination", nRetVal); + //Open dump (if needed) + nRetVal = xnLinkGetEPDumpName(m_nEndpointID, strDumpName, sizeof(strDumpName)); + XN_IS_STATUS_OK_LOG_ERROR("Get EP Dump name", nRetVal); + m_pDumpFile = xnDumpFileOpen(strDumpName, "%s.raw", strDumpName); + //Connect + nRetVal = m_pConnection->Connect(); + XN_IS_STATUS_OK_LOG_ERROR("Connect input data connection", nRetVal); + } + m_nConnected++; + + return XN_STATUS_OK; +} + +void LinkInputDataEndpoint::Disconnect() +{ + xnl::AutoCSLocker lock(m_hCriticalSection); + + if (m_nConnected == 1) + { + //Reference count reaches zero so we REALLY disconnect + xnDumpFileClose(m_pDumpFile); + m_pConnection->Disconnect(); + m_pConnection->SetDataDestination(NULL); + } + + if (m_nConnected > 0) + { + m_nConnected--; + } +} + +XnBool LinkInputDataEndpoint::IsConnected() const +{ + xnl::AutoCSLocker lock(m_hCriticalSection); + return (m_nConnected > 0); +} + +XnStatus LinkInputDataEndpoint::IncomingData(const void* pData, XnUInt32 nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + xnDumpFileWriteBuffer(m_pDumpFile, pData, nSize); + nRetVal = m_pLinkInputStreamsMgr->HandleData(pData, nSize); +// XN_IS_STATUS_OK_LOG_ERROR("Handle data in streams mgr", nRetVal); + if (nRetVal != XN_STATUS_OK) + { + return nRetVal; + } + + return XN_STATUS_OK; +} + + +void LinkInputDataEndpoint::HandleDisconnection() +{ + //TODO: Maybe we need to call disconnect here so the thread will stop + m_nConnected = 0; + m_pNotifications->HandleLinkDataEndpointDisconnection(m_nEndpointID); +} + +XnUInt16 LinkInputDataEndpoint::GetMaxPacketSize() const +{ + return m_pConnection->GetMaxPacketSize(); +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.h new file mode 100644 index 0000000..2c0318a --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputDataEndpoint.h @@ -0,0 +1,58 @@ +#ifndef __XNLINKINPUTDATAENDPOINT_H__ +#define __XNLINKINPUTDATAENDPOINT_H__ + +#include "IAsyncInputConnection.h" +#include "IConnection.h" +#include + +struct XnDumpFile; + +namespace xn +{ + +class IConnectionFactory; +class LinkInputStreamsMgr; + +class ILinkDataEndpointNotifications +{ +public: + virtual ~ILinkDataEndpointNotifications() {} + virtual void HandleLinkDataEndpointDisconnection(XnUInt16 nEndpointID) = 0; +}; + +class LinkInputDataEndpoint : public IDataDestination, public IConnection +{ +public: + LinkInputDataEndpoint(); + virtual ~LinkInputDataEndpoint(); + XnStatus Init(XnUInt16 nEndpointID, + IConnectionFactory* pConnectionFactory, + LinkInputStreamsMgr* pLinkInputStreamsMgr, + ILinkDataEndpointNotifications* pNotifications); + XnBool IsInitialized() const; + + void Shutdown(); + XnStatus Connect(); + void Disconnect(); + XnBool IsConnected() const; + XnUInt16 GetMaxPacketSize() const; + + /* IDataDestination Implementation */ + virtual XnStatus IncomingData(const void* pData, XnUInt32 nSize); + virtual void HandleDisconnection(); + +private: + XnUInt16 m_nEndpointID; + LinkInputStreamsMgr* m_pLinkInputStreamsMgr; + ILinkDataEndpointNotifications* m_pNotifications; + IAsyncInputConnection* m_pConnection; + IConnectionFactory* m_pConnectionFactory; + XnBool m_bInitialized; + volatile XnUInt32 m_nConnected; + XN_CRITICAL_SECTION_HANDLE m_hCriticalSection; + XnDumpFile* m_pDumpFile; +}; + +} + +#endif // __XNLINKINPUTDATAENDPOINT_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.cpp new file mode 100644 index 0000000..6832785 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.cpp @@ -0,0 +1,173 @@ +#include "XnLinkInputStream.h" +#include "XnLinkControlEndpoint.h" + +//Link msg parsers +#include "XnLinkMsgParser.h" + +namespace xn +{ + +LinkInputStream::LinkInputStream() +{ + Reset(); +} + +LinkInputStream::~LinkInputStream() +{ + Shutdown(); +} + +void LinkInputStream::Shutdown() +{ +} + +XnStatus LinkInputStream::Init(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection) +{ + XnStatus nRetVal = XN_STATUS_OK; + XN_VALIDATE_INPUT_PTR(pLinkControlEndpoint); + XN_VALIDATE_INPUT_PTR(pConnection); + if (!pLinkControlEndpoint->IsConnected()) + { + xnLogError(XN_MASK_LINK, "Link control endpoint is not connected"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + m_pLinkControlEndpoint = pLinkControlEndpoint; + m_streamType = streamType; + m_nStreamID = nStreamID; + m_pConnection = pConnection; + m_streamingRefcount = 0; + + /***** Get all stream properties *****/ + nRetVal = m_pLinkControlEndpoint->GetSupportedInterfaces(nStreamID, m_supportedInterfaces); + XN_IS_STATUS_OK_LOG_ERROR("Get stream supported interfaces", nRetVal); + + if (IsInterfaceSupported(XN_LINK_INTERFACE_MIRROR)) + { + nRetVal = m_pLinkControlEndpoint->GetMirror(nStreamID, m_bMirror); + XN_IS_STATUS_OK_LOG_ERROR("Get mirror", nRetVal); + } + + switch (m_streamType) + { + case XN_LINK_STREAM_TYPE_SHIFTS: + m_outputFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + break; + case XN_LINK_STREAM_TYPE_COLOR: + m_outputFormat = ONI_PIXEL_FORMAT_YUV422; + break; + case XN_LINK_STREAM_TYPE_IR: + m_outputFormat = ONI_PIXEL_FORMAT_GRAY16; + break; + default: + m_outputFormat = XN_FORMAT_PASS_THROUGH_UNPACK; + break; + } + + return XN_STATUS_OK; +} + +void LinkInputStream::Reset() +{ + m_pLinkControlEndpoint = NULL; + m_pConnection = NULL; + m_nStreamID = XN_LINK_STREAM_ID_INVALID; + + m_bMirror = FALSE; + m_streamType = XN_LINK_STREAM_TYPE_NONE; + m_outputFormat = XN_FORMAT_PASS_THROUGH_UNPACK; +} + +XnStatus LinkInputStream::Start() +{ + if(++m_streamingRefcount == 1) + { + return StartImpl(); + } + return XN_STATUS_OK; +} + +XnStatus LinkInputStream::Stop() +{ + if(--m_streamingRefcount == 0) + { + return StopImpl(); + } + return XN_STATUS_OK; +} + +XnBool LinkInputStream::IsStreaming() const +{ + return m_bStreaming; +} + +XnUInt16 LinkInputStream::GetStreamID() const +{ + return m_nStreamID; +} + + +XnBool LinkInputStream::IsInterfaceSupported(XnUInt8 nInterfaceID) const +{ + return m_supportedInterfaces.IsSet(nInterfaceID); +} + +XnBool LinkInputStream::GetMirror() const +{ + return m_bMirror; +} + +XnStatus LinkInputStream::SetMirror(XnBool bMirror) +{ + XnStatus nRetVal = m_pLinkControlEndpoint->SetMirror(m_nStreamID, bMirror); + XN_IS_STATUS_OK_LOG_ERROR("Set mirror", nRetVal); + m_bMirror = bMirror; + return XN_STATUS_OK; +} + +LinkMsgParser* LinkInputStream::CreateLinkMsgParser() +{ + if (m_outputFormat == XN_FORMAT_PASS_THROUGH_RAW) + { + return XN_NEW(LinkMsgParser); + } else { + xnLogError(XN_MASK_LINK, "Unknown output format: %d", m_outputFormat); + XN_ASSERT(FALSE); + return NULL; + } +} + +XnBool LinkInputStream::IsOutputFormatSupported(OniPixelFormat format) const +{ + return format == XN_FORMAT_PASS_THROUGH_RAW; +} + +OniPixelFormat LinkInputStream::GetOutputFormat() const +{ + return m_outputFormat; +} + +XnStatus LinkInputStream::SetOutputFormat(OniPixelFormat format) +{ + if (!IsOutputFormatSupported(format)) + { + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + if (m_bStreaming) + { + xnLogWarning(XN_MASK_LINK, "Can't change output format while streaming!"); + return XN_STATUS_INVALID_OPERATION; + } + + m_outputFormat = format; + + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.h new file mode 100644 index 0000000..89705d2 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStream.h @@ -0,0 +1,73 @@ +#ifndef __XNLINKINPUTSTREAM_H__ +#define __XNLINKINPUTSTREAM_H__ + +#include "XnBitSet.h" +#include "XnLinkProtoLibDefs.h" +#include "XnLinkProtoUtils.h" + +namespace xn +{ + +class LinkControlEndpoint; +class LinkMsgParser; +class IConnection; + +class LinkInputStream +{ +public: + LinkInputStream(); + virtual ~LinkInputStream(); + + virtual XnStatus Init(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection); + + virtual XnBool IsInitialized() const = 0; + virtual void Shutdown(); + virtual void Reset(); + + virtual XnStatus Start(); + virtual XnStatus Stop(); + virtual XnBool IsStreaming() const; + virtual XnUInt16 GetStreamID() const; + + typedef void (XN_CALLBACK_TYPE* NewDataAvailableHandler)(void* pCookie); + + virtual XnStatus HandlePacket(const LinkPacketHeader& header, const XnUInt8* pData, XnBool& bPacketLoss) = 0; + + virtual void SetDumpName(const XnChar* strDumpName) = 0; + virtual void SetDumpOn(XnBool bDumpOn) = 0; + + /* Stream Properties */ + virtual XnBool IsOutputFormatSupported(OniPixelFormat format) const; + virtual OniPixelFormat GetOutputFormat() const; + virtual XnStatus SetOutputFormat(OniPixelFormat format); + + virtual XnBool IsInterfaceSupported(XnUInt8 nInterfaceID) const; + + virtual XnStreamFragLevel GetStreamFragLevel() const = 0; + + virtual XnBool GetMirror() const; + virtual XnStatus SetMirror(XnBool bMirror); + +protected: + virtual XnStatus StartImpl() = 0; + virtual XnStatus StopImpl() = 0; + + virtual LinkMsgParser* CreateLinkMsgParser(); + LinkControlEndpoint* m_pLinkControlEndpoint; + IConnection* m_pConnection; + XnStreamType m_streamType; + XnUInt16 m_nStreamID; + OniPixelFormat m_outputFormat; + volatile XnBool m_bStreaming; + int m_streamingRefcount; + + xnl::BitSet m_supportedInterfaces; + XnBool m_bMirror; +}; + +} + +#endif // __XNLINKINPUTSTREAM_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.cpp new file mode 100644 index 0000000..1decd93 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.cpp @@ -0,0 +1,338 @@ +#include "XnLinkInputStreamsMgr.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkStatusCodes.h" +#include "XnLinkFrameInputStream.h" +#include "XnLinkProtoLibDefs.h" +#include "XnLinkContInputStream.h" +#include "XnLinkControlEndpoint.h" +#include +#include + +namespace xn +{ + + +/* FRAG_FLAGS_ALLOWED_CHANGES[y][x] is 1 if a change from fragmentation flag y to x is allowed, 0 otherwise. */ +const XnUInt32 LinkInputStreamsMgr::FRAG_FLAGS_ALLOWED_CHANGES[4][4] = { +/* M, B, E, S */ +/* Allowed state changes from MIDDLE: */ {1, 0, 1, 0}, +/* Allowed state changes from BEGIN: */ {1, 0, 1, 0}, +/* Allowed state changes from END: */ {0, 1, 0, 1}, +/* Allowed state changes from SINGLE: */ {0, 1, 0, 1}, +}; +const XnUInt16 LinkInputStreamsMgr::INITIAL_PACKET_ID = 1; + +LinkInputStreamsMgr::LinkInputStreamsMgr() +{ + xnOSMemSet(&m_streamInfos, 0, sizeof(m_streamInfos)); +} + +LinkInputStreamsMgr::~LinkInputStreamsMgr() +{ + Shutdown(); +} + +XnStatus LinkInputStreamsMgr::Init() +{ + return XN_STATUS_OK; +} + +void LinkInputStreamsMgr::Shutdown() +{ + for (XnUInt16 nStreamID = 0; nStreamID < XN_LINK_MAX_STREAMS; nStreamID++) + { + ShutdownInputStream(nStreamID); + } +} + +void LinkInputStreamsMgr::RegisterStreamOfType(XnStreamType streamType, const XnChar* strCreationInfo, XnUInt16 nStreamID) +{ + if (m_streamInfos[nStreamID].pInputStream == NULL || + (m_streamInfos[nStreamID].refCount > 0 && nStreamID != FindStreamByType(streamType,strCreationInfo))) + { + xnLogWarning(XN_MASK_LINK, "Trying to register a non existing Input stream %u", nStreamID); + XN_ASSERT(false); + return; + } + + if (m_streamInfos[nStreamID].refCount == 0) + { + m_streamInfos[nStreamID].streamType = streamType; + m_streamInfos[nStreamID].strCreationInfo = strCreationInfo; + } + //increase refcounter + ++m_streamInfos[nStreamID].refCount; + xnLogVerbose(XN_MASK_LINK, "Input stream %u incref. refcount is %d", nStreamID, m_streamInfos[nStreamID].refCount); +} + + +XnBool LinkInputStreamsMgr::UnregisterStream(XnUInt16 nStreamID) +{ + XnBool wasLast = false; + + if (m_streamInfos[nStreamID].pInputStream == NULL || m_streamInfos[nStreamID].refCount <= 0) + { + xnLogWarning(XN_MASK_LINK, "Trying to unregister a non existing Input stream %u", nStreamID); + XN_ASSERT(false); + return false; + } + + //decrease refcounter + if (--m_streamInfos[nStreamID].refCount == 0) + { + wasLast = true; + } + xnLogVerbose(XN_MASK_LINK, "Input stream %u decref. refcount is %d", nStreamID, m_streamInfos[nStreamID].refCount); + + return wasLast; +} + +XnBool LinkInputStreamsMgr::HasStreamOfType(XnStreamType streamType, const XnChar* strCreationInfo, XnUInt16& nStreamID) +{ + int i; + if ((i = FindStreamByType(streamType, strCreationInfo)) >= 0) + { + nStreamID = (XnUInt16)i; + return true; + } + return false; +} + +int LinkInputStreamsMgr::FindStreamByType(XnStreamType streamType, const XnChar* strCreationInfo) +{ + for (int i = 0; i < XN_LINK_MAX_STREAMS; ++i) + { + if (m_streamInfos[i].refCount > 0 && + streamType == m_streamInfos[i].streamType && + ((m_streamInfos[i].strCreationInfo == NULL && strCreationInfo == NULL) || xnOSStrCmp(strCreationInfo, m_streamInfos[i].strCreationInfo) == 0)) + { + return i; + } + } + return -1; +} + +XnStatus LinkInputStreamsMgr::InitInputStream(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnStreamFragLevel streamFragLevel = XN_LINK_STREAM_FRAG_LEVEL_NONE; + if (nStreamID > XN_LINK_MAX_STREAMS) + { + xnLogError(XN_MASK_LINK, "Cannot initialize stream of id %u - max stream id is %u", + nStreamID, XN_LINK_MAX_STREAMS-1); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_STREAM_ID; + } + + nRetVal = pLinkControlEndpoint->GetStreamFragLevel(nStreamID, streamFragLevel); + XN_IS_STATUS_OK_LOG_ERROR("Get stream frag level", nRetVal); + + if (m_streamInfos[nStreamID].pInputStream == NULL) + { + //Need to create the input stream for the first time + switch (streamFragLevel) + { + case XN_LINK_STREAM_FRAG_LEVEL_FRAMES: + m_streamInfos[nStreamID].pInputStream = XN_NEW(LinkFrameInputStream); + break; + case XN_LINK_STREAM_FRAG_LEVEL_CONTINUOUS: + m_streamInfos[nStreamID].pInputStream = XN_NEW(LinkContInputStream); + break; + + default: + xnLogError(XN_MASK_LINK, "Bad stream type %u", streamFragLevel); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + } + + XN_VALIDATE_ALLOC_PTR(m_streamInfos[nStreamID].pInputStream); + + StreamInfo& streamInfo = m_streamInfos[nStreamID]; + if (m_streamInfos[nStreamID].pInputStream->IsInitialized() && (streamInfo.streamFragLevel != streamFragLevel)) + { + XN_DELETE(m_streamInfos[nStreamID].pInputStream); + m_streamInfos[nStreamID].pInputStream = NULL; + xnLogError(XN_MASK_LINK, + "Stream %u was already initialized with stream type %u, but now tried to initialize it with stream type %u :(", + nStreamID, streamInfo.streamFragLevel, streamFragLevel); + /*Streams may only be re-initialized with the same stream type. This means a frame stream must always + remain a frame stream and a continuous stream must always remain a continuous stream. */ + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + nRetVal = m_streamInfos[nStreamID].pInputStream->Init(pLinkControlEndpoint, streamType, nStreamID, pConnection); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(m_streamInfos[nStreamID].pInputStream); + m_streamInfos[nStreamID].pInputStream = NULL; + xnLogError(XN_MASK_LINK, "Failed to Initialize link input stream: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return nRetVal; + } + + streamInfo.nMsgType = 0; + streamInfo.nNextPacketID = INITIAL_PACKET_ID; + streamInfo.streamFragLevel = streamFragLevel; + streamInfo.prevFragmentation = XN_LINK_FRAG_END; // this means we now expect BEGIN + streamInfo.packetLoss = FALSE; + return XN_STATUS_OK; +} + +void LinkInputStreamsMgr::ShutdownInputStream(XnUInt16 nStreamID) +{ + LinkInputStream* pLinkInputStream = GetInputStream(nStreamID); + if (pLinkInputStream != NULL) + { + pLinkInputStream->Shutdown(); + XN_DELETE(pLinkInputStream); + m_streamInfos[nStreamID].pInputStream = NULL; + } +} + +void LinkInputStreamsMgr::HandlePacket(const LinkPacketHeader* pLinkPacketHeader) +{ + //Validate Stream ID + XnUInt16 nStreamID = pLinkPacketHeader->GetStreamID(); + if (nStreamID >= XN_LINK_MAX_STREAMS) + { + xnLogWarning(XN_MASK_LINK, "Got bad Stream ID: %u, max StreamID is %u", nStreamID, XN_LINK_MAX_STREAMS-1); + XN_ASSERT(FALSE); + return; + } + + StreamInfo* pStreamInfo = &m_streamInfos[nStreamID]; + + //Validate packet ID + XnUInt16 nPacketID = pLinkPacketHeader->GetPacketID(); + if (nPacketID != pStreamInfo->nNextPacketID) + { + xnLogWarning(XN_MASK_LINK, "Expected packet id of %u but got %u on stream %u.", + pStreamInfo->nNextPacketID, nPacketID, nStreamID); + pStreamInfo->packetLoss = TRUE; + } + + //We now expect the packet ID to be right after the one we got (even if we lost some packets on the way). + pStreamInfo->nNextPacketID = (nPacketID + 1); + + XnUInt16 nMsgType = pLinkPacketHeader->GetMsgType(); + XnLinkFragmentation fragmentation = pLinkPacketHeader->GetFragmentationFlags(); + + if (!pStreamInfo->packetLoss && !FRAG_FLAGS_ALLOWED_CHANGES[pStreamInfo->prevFragmentation][fragmentation]) + { + //The transition between the previous fragmentation flags and the current fragmentation flags is not allowed + xnLogWarning(XN_MASK_LINK, "Packet %u in stream %u has fragmentation flags of %s, but previous packet in this stream was %s", + nPacketID, nStreamID, + xnFragmentationFlagsToStr(fragmentation), + xnFragmentationFlagsToStr(pStreamInfo->prevFragmentation)); + XN_ASSERT(FALSE); + pStreamInfo->packetLoss = TRUE; + } + + pStreamInfo->prevFragmentation = fragmentation; + + if (fragmentation & XN_LINK_FRAG_BEGIN) + { + //Set message type for new frame + pStreamInfo->nMsgType = nMsgType; + } + else + { + //Validate that message type is consistent with first packet in frame. + if (!pStreamInfo->packetLoss && nMsgType != pStreamInfo->nMsgType) + { + xnLogWarning(XN_MASK_LINK, "Inconsistent msg type for stream %u - expected 0x%04X but got 0x%04X", + nStreamID, pStreamInfo->nMsgType, nMsgType); + XN_ASSERT(FALSE); + pStreamInfo->packetLoss = TRUE; + return; + } + } + + if (!pStreamInfo->pInputStream->IsStreaming()) + { + xnLogWarning(XN_MASK_LINK, "Stream %u got packets but it is not streaming", nStreamID); + XN_ASSERT(FALSE); + return; + } + + // the data is immediately after the header + const XnUInt8* pPacketData = reinterpret_cast(pLinkPacketHeader + 1); + XnStatus nRetVal = pStreamInfo->pInputStream->HandlePacket(*pLinkPacketHeader, pPacketData, pStreamInfo->packetLoss); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "Failed to handle packet of %u bytes in stream %u: %s", + pLinkPacketHeader->GetDataSize(), nStreamID, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return; + } +} + +XnStatus LinkInputStreamsMgr::HandleData(const void* pData, XnUInt32 nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nBytesToRead = nSize; + + XN_PROFILING_START_SECTION("LinkInputStreamsMgr::HandleData()"); + + const XnUInt8* pRawLinkPacket = reinterpret_cast(pData); + + while (nBytesToRead > 0) + { + const LinkPacketHeader* pLinkPacketHeader = reinterpret_cast(pRawLinkPacket); + + //Validate basic info in packet header + nRetVal = pLinkPacketHeader->Validate(nBytesToRead); + XN_IS_STATUS_OK_LOG_ERROR("Validate packet", nRetVal); + + pRawLinkPacket += pLinkPacketHeader->GetSize(); + nBytesToRead -= pLinkPacketHeader->GetSize(); + + HandlePacket(pLinkPacketHeader); + } + + XN_PROFILING_END_SECTION; + + return XN_STATUS_OK; +} + +LinkInputStream* LinkInputStreamsMgr::GetInputStream(XnUInt16 nStreamID) +{ + if (nStreamID >= XN_LINK_MAX_STREAMS) + { + XN_ASSERT(FALSE); + return NULL; + } + + return m_streamInfos[nStreamID].pInputStream; +} + +const LinkInputStream* LinkInputStreamsMgr::GetInputStream(XnUInt16 nStreamID) const +{ + if (nStreamID >= XN_LINK_MAX_STREAMS) + { + XN_ASSERT(FALSE); + return NULL; + } + + return m_streamInfos[nStreamID].pInputStream; +} + +XnBool LinkInputStreamsMgr::HasStreams() const +{ + for (int i = 0; i < XN_LINK_MAX_STREAMS; ++i) + { + if (m_streamInfos[i].pInputStream != NULL) + { + return TRUE; + } + } + + return FALSE; +} + +} //namespace xn diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.h new file mode 100644 index 0000000..d89e945 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkInputStreamsMgr.h @@ -0,0 +1,69 @@ +#ifndef __LINKINPUTSTREAMSMGR_H__ +#define __LINKINPUTSTREAMSMGR_H__ + +#include "XnLinkDefs.h" +#include "XnLinkProtoLibDefs.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkInputStream.h" +#include +#include + +namespace xn +{ + +class LinkControlEndpoint; +class IConnection; + +class LinkInputStreamsMgr +{ + +public: + LinkInputStreamsMgr(); + ~LinkInputStreamsMgr(); + + XnStatus Init(); + void Shutdown(); + + void RegisterStreamOfType(XnStreamType streamType, const XnChar* strCreationInfo, XnUInt16 nStreamID); + XnBool UnregisterStream(XnUInt16 nStreamID); // returns true if the unregistered stream was the last one + XnBool HasStreamOfType(XnStreamType streamType, const XnChar* strCreationInfo, XnUInt16& nStreamID); + + XnStatus InitInputStream(LinkControlEndpoint* pLinkControlEndpoint, + XnStreamType streamType, + XnUInt16 nStreamID, + IConnection* pConnection); + + void ShutdownInputStream(XnUInt16 nStreamID); + XnStatus HandleData(const void* pData, XnUInt32 nSize); + const LinkInputStream* GetInputStream(XnUInt16 nStreamID) const; + LinkInputStream* GetInputStream(XnUInt16 nStreamID); + + XnBool HasStreams() const; + +private: + void HandlePacket(const LinkPacketHeader* pLinkPacketHeader); + int FindStreamByType(XnStreamType streamType, const XnChar* strCreationInfo); //returns found streamId, or -1 + + static const XnUInt32 FRAG_FLAGS_ALLOWED_CHANGES[4][4]; + static const XnUInt16 INITIAL_PACKET_ID; + + struct StreamInfo + { + XnUInt16 nNextPacketID; + XnUInt16 nMsgType; + XnLinkFragmentation prevFragmentation; + XnStreamFragLevel streamFragLevel; + LinkInputStream* pInputStream; + XnBool packetLoss; + + XnStreamType streamType; + const XnChar* strCreationInfo; + int refCount; + }; + + StreamInfo m_streamInfos[XN_LINK_MAX_STREAMS]; +}; + +} + +#endif // __LINKINPUTSTREAMSMGR_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.cpp new file mode 100644 index 0000000..97868c7 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.cpp @@ -0,0 +1,201 @@ +#include "XnLinkLogParser.h" +#include "XnLinkProtoUtils.h" +#include + +namespace xn +{ + +LinkLogParser::LinkLogParser() : m_copyDataToOutput(false) +{ +} + +LinkLogParser::~LinkLogParser() +{ + //Close any open log files + for(xnl::Hash::Iterator iter = m_activeLogs.Begin(); iter!=m_activeLogs.End(); ++iter) + xnDumpFileClose(iter->Value()); + m_activeLogs.Clear(); + +} + +XnStatus LinkLogParser::ParsePacketImpl(XnLinkFragmentation /*fragmentation*/, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + //Copy data to output buffer if needed (The log dumps data anyway, so most time we wont need it and save the memcopy + //Otherwise, we do not advance pDst, so Data size remains 0 + if(m_copyDataToOutput) + { + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + if (pDst + nPacketDataSize > pDstEnd) + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(pDst, pSrc, nPacketDataSize); + pDst += nPacketDataSize; + } + + XnStatus nRetVal = XN_STATUS_OK; + XnLinkLogParam* logHeader = (XnLinkLogParam*)pSrc; + + //Parsed data + XnUInt8 fileID; + XnLinkLogCommand command; + XnChar logFileName[XN_LINK_MAX_LOG_FILE_NAME_LENGTH]; + + fileID = logHeader->m_ID; + command = (XnLinkLogCommand)logHeader->command; + + XnUInt16 actualDataSize = XN_PREPARE_VAR16_IN_BUFFER(logHeader->size); //We may have padding at the end + + pSrc += sizeof(XnLinkLogParam); + actualDataSize -= sizeof(XnLinkLogParam); + + //Parse file name + if(command == XN_LINK_LOG_COMMAND_OPEN || command == XN_LINK_LOG_COMMAND_OPEN_APPEND) + { + //Copy file name to our XnChar array + XnUInt8* inputFileName = ((XnLinkLogFileParam*)pSrc)->logFileName; + int i = 0; + for(; i < XN_LINK_MAX_LOG_FILE_NAME_LENGTH && *inputFileName != '\0'; ++i, ++inputFileName) + logFileName[i] = (XnChar)*inputFileName; + logFileName[i] = '\0'; + + pSrc += sizeof(XnLinkLogFileParam); + actualDataSize -= sizeof(XnLinkLogFileParam); + } + + //Write the data to the matching file + switch (command) + { + case XN_LINK_LOG_COMMAND_OPEN_APPEND: + nRetVal = XN_STATUS_NOT_IMPLEMENTED; + if (nRetVal != XN_STATUS_OK) + { + xnLoggerError(XN_LOGGER_RETVAL_CHECKS, "Failed to Append log file \'%s\': %s", logFileName, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return (nRetVal); + } + break; + case XN_LINK_LOG_COMMAND_OPEN: + xnLogVerbose("", "Received open command for file %s id %d\n", logFileName, fileID); + nRetVal = OpenLogFile(fileID, logFileName); + if (nRetVal != XN_STATUS_OK) + { + xnLoggerError(XN_LOGGER_RETVAL_CHECKS, "Failed to Open log file \'%s\': %s", logFileName, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return (nRetVal); + } + break; + case XN_LINK_LOG_COMMAND_CLOSE: + xnLogVerbose("", "Received close command for file id %d\n", fileID); + nRetVal = CloseLogFile(fileID); + if (nRetVal != XN_STATUS_OK) + { + xnLoggerError(XN_LOGGER_RETVAL_CHECKS, "Failed to Close log file #%d: %s", fileID, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return (nRetVal); + } + break; + case XN_LINK_LOG_COMMAND_WRITE: + nRetVal = WriteToLogFile(fileID, pSrc, actualDataSize); + if (nRetVal != XN_STATUS_OK) + { + xnLoggerError(XN_LOGGER_RETVAL_CHECKS, "Failed to Write log file #%d: %s", fileID, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return (nRetVal); + } + break; + default: + xnLogWarning(XN_MASK_LINK, "Invalid command: %d", (int)command); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + return XN_STATUS_OK; +} + + + +XnStatus LinkLogParser::OpenLogFile( XnUInt8 fileID, const XnChar* fileName ) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnDumpFile* pTargetFile; + + //We should not have a file with this ID + if(m_activeLogs.Find(fileID) != m_activeLogs.End()) + { + xnLogWarning(XN_MASK_LINK, "Attempting to open existing log file. ID: %d, name: %s", (int)fileID, fileName); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + //Prefix current timestamp to file name (timestamp is 25 chars) + XnChar filenameWithTimestamp[XN_LINK_MAX_LOG_FILE_NAME_LENGTH + 25 + 1]; + time_t currtime; + time(&currtime); + strftime(filenameWithTimestamp, sizeof(filenameWithTimestamp)-1, "%Y_%m_%d__%H_%M_%S.", localtime(&currtime)); + xnOSStrAppend(filenameWithTimestamp, fileName, sizeof(filenameWithTimestamp)-1); + + //Open file and add to collection + pTargetFile = xnDumpFileOpenEx("", true, false, filenameWithTimestamp); + if(pTargetFile == NULL) + nRetVal = XN_STATUS_ERROR; + + if(nRetVal == XN_STATUS_OK) + m_activeLogs[fileID] = pTargetFile; + + return nRetVal; +} + +XnStatus LinkLogParser::CloseLogFile( XnUInt8 fileID ) +{ + XnStatus nRetVal = XN_STATUS_OK; + + //We should have a file with this ID + xnl::Hash::Iterator fileRecord = m_activeLogs.Find(fileID); + + if(fileRecord == m_activeLogs.End()) + { + xnLogWarning(XN_MASK_LINK, "Attempting to close non existing log file. ID: %d", (int)fileID); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + XnDumpFile* pTargetFile = fileRecord->Value(); + + //Close file and remove from collection + xnDumpFileClose(pTargetFile); + m_activeLogs.Remove(fileRecord); + + return nRetVal; + +} + +XnStatus LinkLogParser::WriteToLogFile( XnUInt8 fileID, const void* pData, XnUInt32 dataLength ) +{ + //We should have a file with this ID + xnl::Hash::Iterator fileRecord = m_activeLogs.Find(fileID); + + if(fileRecord == m_activeLogs.End()) + { + xnLogWarning(XN_MASK_LINK, "Attempting to write to non existing log file. ID: %d", (int)fileID); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + xnDumpFileWriteBuffer(fileRecord->Value(), pData, dataLength); + return XN_STATUS_OK; +} + +void LinkLogParser::GenerateOutputBuffer(bool toCreate) +{ + m_copyDataToOutput = toCreate; +} + + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.h new file mode 100644 index 0000000..ff8ca4c --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkLogParser.h @@ -0,0 +1,35 @@ +#ifndef _XNLINKLOGPARSER_H_ +#define _XNLINKLOGPARSER_H_ + +#include "XnLinkMsgParser.h" +#include + +namespace xn +{ + +class LinkLogParser : public LinkMsgParser +{ +public: + LinkLogParser(); + virtual ~LinkLogParser(); + + void GenerateOutputBuffer(bool toCreate); + +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + + XnStatus WriteToLogFile( XnUInt8 fileID, const void* pData, XnUInt32 dataLength ); + XnStatus CloseLogFile( XnUInt8 fileID ); + XnStatus OpenLogFile( XnUInt8 fileID, const XnChar* fileName ); +private: + xnl::Hash m_activeLogs; + bool m_copyDataToOutput; +}; + +} + +#endif // _XNLINKLOGPARSER_H_ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.cpp new file mode 100644 index 0000000..b6139e4 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.cpp @@ -0,0 +1,175 @@ +#include "XnLinkMsgEncoder.h" +#include "XnLinkProtoUtils.h" +#include +#include + +namespace xn +{ + +LinkMsgEncoder::LinkMsgEncoder() +{ + m_nMaxMsgSize = 0; + m_nMaxPacketSize = 0; + m_nMaxNumPackets = 0; + m_nBufferSize = 0; + m_pCurrPacket = NULL; + m_nEncodedSize = 0; + m_pOutputBuffer = NULL; + xnOSMemSet(&m_packetHeader, 0, sizeof(m_packetHeader)); + + //Set constant packet header values - for all messages + m_packetHeader.SetMagic(); + m_packetHeader.SetSize(sizeof(m_packetHeader)); //this is the minimum size, we'll add to this. +} + +LinkMsgEncoder::~LinkMsgEncoder() +{ + Shutdown(); +} + +XnStatus LinkMsgEncoder::Init(XnUInt32 nMaxMsgSize, XnUInt16 nMaxPacketSize) +{ + if (nMaxPacketSize == 0) + { + xnLogError(XN_MASK_LINK, "Got max packet size of 0 in link msg encoder init :("); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + m_nMaxMsgSize = nMaxMsgSize; + m_nMaxPacketSize = nMaxPacketSize; + + XnUInt16 nMaxPacketDataSize = m_nMaxPacketSize - sizeof(LinkPacketHeader); + m_nMaxNumPackets = m_nMaxMsgSize / nMaxPacketDataSize; + if (m_nMaxPacketSize % nMaxPacketDataSize != 0) + { + //We need one more packet for remainder of data + m_nMaxNumPackets++; + } + m_nBufferSize = m_nMaxNumPackets * m_nMaxPacketSize; + m_pOutputBuffer = reinterpret_cast(xnOSMallocAligned(m_nBufferSize, XN_DEFAULT_MEM_ALIGN)); + XN_VALIDATE_ALLOC_PTR(m_pOutputBuffer); + + return XN_STATUS_OK; +} + +void LinkMsgEncoder::Shutdown() +{ + xnOSFreeAligned(m_pOutputBuffer); + m_pOutputBuffer = NULL; + m_nMaxMsgSize = 0; +} + +void LinkMsgEncoder::BeginEncoding(XnUInt16 nMsgType, + XnUInt16 nBasePacketID, + XnUInt16 nStreamID, + XnLinkFragmentation firstPacketFrag /*= XN_LINK_FRAG_BEGIN*/, + XnUInt16 nCID /*= 0*/) +{ + //Initialize m_packetHeader values for all packets in this message. + m_packetHeader.SetMsgType(nMsgType); + m_packetHeader.SetPacketID(nBasePacketID); //We'll increment this every time we advance m_pCurrPacket + m_packetHeader.SetStreamID(nStreamID); //This is the same for all packets + m_packetHeader.SetFragmentationFlags(XN_LINK_FRAG_MIDDLE); //(for MOST packets) + m_packetHeader.SetCID(nCID); + + //Point to first packet - this also sets m_pCurrPacket. + m_pCurrPacketBuffer = m_pOutputBuffer; + + //Copy prepared packet header into first destination packet + xnOSMemCopy(m_pCurrPacket, &m_packetHeader, sizeof(m_packetHeader)); + + //Set values specific to first packet + m_pCurrPacket->SetFragmentationFlags(firstPacketFrag); + + //Minimum size is always one packet header + m_nEncodedSize = sizeof(LinkPacketHeader); +} + +void LinkMsgEncoder::EncodeData(const void* pSourceData, XnUInt32 nSize) +{ + XnUInt16 nPacketRemainingSpace = 0; //Remaining space in current packet in each iteration + XnUInt16 nPacketBytesToCopy = 0; //Number of bytes to copy to current packet in each iteration + XnUInt32 nBytesLeftToCopy = nSize; //Total number of bytes left to copy + const XnUInt8* pCurrData = reinterpret_cast(pSourceData); //Current source data pointer + while (nBytesLeftToCopy > 0) + { + if (m_pCurrPacket->GetSize() == m_nMaxPacketSize) + { + //Current packet is full. Move to next packet (this also advances m_pCurrPacket). + m_pCurrPacketBuffer += m_nMaxPacketSize; + if (m_pCurrPacketBuffer >= m_pOutputBuffer + m_nBufferSize) + { + xnLogError(XN_MASK_LINK, "Msg encoder buffer overrun :( Was about to write to position %u, but buffer size is only %u", + (m_pCurrPacketBuffer - m_pOutputBuffer), m_nBufferSize); + XN_ASSERT(FALSE); + return; + } + //Advance packet ID + m_packetHeader.SetPacketID(m_packetHeader.GetPacketID() + 1); + /*Copy prepared packet header into destination packet. This also sets m_pCurrPacket->m_nSize to minimum + and m_pCurrPacket->m_nFragmentation to XN_LINK_FRAG_MIDDLE.*/ + xnOSMemCopy(m_pCurrPacket, &m_packetHeader, sizeof(m_packetHeader)); + //Increase encoded size for packet header + m_nEncodedSize += sizeof(m_packetHeader); + } + //Calculate remaining space in current packet + nPacketRemainingSpace = m_nMaxPacketSize - m_pCurrPacket->GetSize(); + //Calculate how many bytes we're copying to the current packet + nPacketBytesToCopy = static_cast(XN_MIN(nPacketRemainingSpace, nBytesLeftToCopy)); + + /************ Copy data to current packet ********************/ + xnOSMemCopy(m_pCurrPacketBuffer + m_pCurrPacket->GetSize(), + pCurrData, + nPacketBytesToCopy); + /*************************************************************/ + + //Advance current source data pointer + pCurrData += nPacketBytesToCopy; + //Increase encoded size for packet data + m_nEncodedSize += nPacketBytesToCopy; + //Increase size of current packet + m_pCurrPacket->SetSize(m_pCurrPacket->GetSize() + nPacketBytesToCopy); + //Decrease number of bytes we have left to copy + nBytesLeftToCopy -= nPacketBytesToCopy; + } +} + +void LinkMsgEncoder::EndEncoding(XnLinkFragmentation lastPacketFrag /*= XN_LINK_FRAG_END*/) +{ + //Add END flag to fragmentation flags if it's set + m_pCurrPacket->SetFragmentationFlags( + XnLinkFragmentation(m_pCurrPacket->GetFragmentationFlags() | (lastPacketFrag & XN_LINK_FRAG_END))); +} + +const void* LinkMsgEncoder::GetEncodedData() const +{ + return m_pOutputBuffer; +} + +XnUInt32 LinkMsgEncoder::GetEncodedSize() const +{ + return m_nEncodedSize; +} + + +XnUInt16 LinkMsgEncoder::GetMaxPacketSize() const +{ + return m_nMaxPacketSize; +} + +XnUInt16 LinkMsgEncoder::GetPacketID() const +{ + return m_pCurrPacket->GetPacketID(); +} + +XnUInt32 LinkMsgEncoder::GetMaxMsgSize() const +{ + return m_nMaxMsgSize; +} + +void LinkMsgEncoder::SetPacketID(XnUInt16 nPacketID) +{ + m_pCurrPacket->SetPacketID(nPacketID); +} +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.h new file mode 100644 index 0000000..0b835d7 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgEncoder.h @@ -0,0 +1,63 @@ +#ifndef __XNLINKENCODEDDATAUNIT_H__ +#define __XNLINKENCODEDDATAUNIT_H__ + +#include "XnLinkDefs.h" +#include "XnLinkProto.h" +#include "XnLinkProtoUtils.h" +#include + +namespace xn +{ + +class LinkMsgEncoder +{ +public: + LinkMsgEncoder(); + virtual ~LinkMsgEncoder(); + + //nMaxMsgSize includes all message headers (not link layer headers). nPacketSize is total link layer packet size, including link layer header. + virtual XnStatus Init(XnUInt32 nMaxMsgSize, XnUInt16 nPacketSize); + virtual void Shutdown(); + virtual void BeginEncoding(XnUInt16 nMsgType, + XnUInt16 nBasePacketID, + XnUInt16 nStreamID, + XnLinkFragmentation firstPacketFrag = XN_LINK_FRAG_BEGIN, + XnUInt16 nCID = 0); + + virtual void EncodeData(const void* pSourceData, XnUInt32 nSize); + virtual void EndEncoding(XnLinkFragmentation lastPacketFrag = XN_LINK_FRAG_END); + + virtual const void* GetEncodedData() const; + virtual XnUInt32 GetEncodedSize() const; + virtual XnUInt32 GetMaxMsgSize() const; + virtual XnUInt16 GetMaxPacketSize() const; + + //Returns the packet ID of the current packet. Only valid after encoding. + virtual XnUInt16 GetPacketID() const; + + //This sets the packet ID of the CURRENT packet only. Only valid after encoding. + virtual void SetPacketID(XnUInt16 nPacketID); + +private: + XnUInt32 m_nMaxMsgSize; + XnUInt16 m_nMaxPacketSize; + XnUInt32 m_nMaxNumPackets; + XnUInt32 m_nBufferSize; + + /******* This points to the actual encoded data ******/ + XnUInt8* m_pOutputBuffer; + /*****************************************************/ + + union + { + XnUInt8* m_pCurrPacketBuffer; + LinkPacketHeader* m_pCurrPacket; + }; + LinkPacketHeader m_packetHeader; + XnUInt32 m_nEncodedSize; +}; + +} + + +#endif // __XNLINKENCODEDDATAUNIT_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.cpp new file mode 100644 index 0000000..faacfe7 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.cpp @@ -0,0 +1,87 @@ +#include "XnLinkMsgParser.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkDefs.h" +#include +#include + +namespace xn +{ + +LinkMsgParser::LinkMsgParser() +{ + m_pDestBuffer = NULL; + m_pCurrDest = NULL; + m_pDestEnd = NULL; +} + +LinkMsgParser::~LinkMsgParser() +{ + Shutdown(); +} + +XnStatus LinkMsgParser::Init() +{ + return XN_STATUS_OK; +} + +void LinkMsgParser::Shutdown() +{ + m_pDestBuffer = NULL; + m_pCurrDest = NULL; + m_pDestEnd = NULL; +} + +XnStatus LinkMsgParser::BeginParsing(void* pDestBuffer, XnUInt32 nDestBufferSize) +{ + XN_VALIDATE_INPUT_PTR(pDestBuffer); + m_pDestBuffer = reinterpret_cast(pDestBuffer); + m_pCurrDest = m_pDestBuffer; + m_pDestEnd = m_pDestBuffer + nDestBufferSize; + + return XN_STATUS_OK; +} + +XnStatus LinkMsgParser::ParsePacket(const LinkPacketHeader& header, const XnUInt8* pData) +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = ParsePacketImpl(header.GetFragmentationFlags(), pData, pData + header.GetDataSize(), m_pCurrDest, m_pDestEnd); + XN_IS_STATUS_OK(nRetVal); + + return XN_STATUS_OK; +} + +const void* LinkMsgParser::GetParsedData() const +{ + return m_pDestBuffer; +} + +XnUInt32 LinkMsgParser::GetParsedSize() const +{ + return XnUInt32(m_pCurrDest - m_pDestBuffer); +} + +XnUInt32 LinkMsgParser::GetBufferSize() const +{ + return XnUInt32(m_pDestEnd - m_pDestBuffer); +} + +XnStatus LinkMsgParser::ParsePacketImpl(XnLinkFragmentation /*fragmentation*/, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + if (pDst + nPacketDataSize > pDstEnd) + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + xnOSMemCopy(pDst, pSrc, nPacketDataSize); + pDst += nPacketDataSize; + + return XN_STATUS_OK; +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.h new file mode 100644 index 0000000..f46cff2 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkMsgParser.h @@ -0,0 +1,40 @@ +#ifndef __XNLINKMSGPARSER_H__ +#define __XNLINKMSGPARSER_H__ + +#include +#include "XnLinkProtoUtils.h" + +namespace xn +{ + +class LinkMsgParser +{ +public: + LinkMsgParser(); + virtual ~LinkMsgParser(); + virtual XnStatus Init(); + virtual void Shutdown(); + + XnStatus BeginParsing(void* pDestBuffer, XnUInt32 nDestBufferSize); + XnStatus ParsePacket(const LinkPacketHeader& header, const XnUInt8* pData); + + const void* GetParsedData() const; + XnUInt32 GetParsedSize() const; + XnUInt32 GetBufferSize() const; + +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + +private: + XnUInt8* m_pDestBuffer; + XnUInt8* m_pCurrDest; + XnUInt8* m_pDestEnd; +}; + +} + +#endif // __XNLINKMSGPARSER_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.cpp new file mode 100644 index 0000000..5940937 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.cpp @@ -0,0 +1,108 @@ +#include "XnLinkOutputDataEndpoint.h" +#include "IConnectionFactory.h" +#include "IOutputConnection.h" +#include +#include + +#define XN_MASK_LINK "xnLink" + +namespace xn +{ + + +LinkOutputDataEndpoint::LinkOutputDataEndpoint() +{ + m_pConnection = NULL; + m_bInitialized = FALSE; + m_bConnected = FALSE; + m_nEndpointID = 0; +} + +LinkOutputDataEndpoint::~LinkOutputDataEndpoint() +{ + Shutdown(); +} + +XnStatus LinkOutputDataEndpoint::Init(XnUInt16 nEndpointID, + IConnectionFactory* pConnectionFactory) +{ + XN_VALIDATE_INPUT_PTR(pConnectionFactory); + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bInitialized) + { + //Create output data connection + m_nEndpointID = nEndpointID; + + nRetVal = pConnectionFactory->CreateOutputDataConnection(nEndpointID, m_pConnection); + XN_IS_STATUS_OK_LOG_ERROR("Create output data connection", nRetVal); + + //We are initialized :) + m_bInitialized = TRUE; + } + + return XN_STATUS_OK; +} + + +XnBool LinkOutputDataEndpoint::IsInitialized() const +{ + return m_bInitialized; +} + +void LinkOutputDataEndpoint::Shutdown() +{ + Disconnect(); + XN_DELETE(m_pConnection); + m_pConnection = NULL; + m_bInitialized = FALSE; +} + +XnStatus LinkOutputDataEndpoint::Connect() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bInitialized) + { + XN_LOG_ERROR_RETURN(XN_STATUS_NOT_INIT, XN_MASK_LINK, "Not initialized"); + } + + if (!m_bConnected) + { + //Connect + nRetVal = m_pConnection->Connect(); + XN_IS_STATUS_OK_LOG_ERROR("Connect input data connection", nRetVal); + + //We're connected + m_bConnected = TRUE; + } + + return XN_STATUS_OK; +} + +void LinkOutputDataEndpoint::Disconnect() +{ + if (m_bConnected) + { + m_pConnection->Disconnect(); + m_bConnected = FALSE; + } +} + +XnBool LinkOutputDataEndpoint::IsConnected() const +{ + return m_bConnected; +} + + +XnUInt16 LinkOutputDataEndpoint::GetMaxPacketSize() const +{ + return m_pConnection->GetMaxPacketSize(); +} + +XnStatus LinkOutputDataEndpoint::SendData(const void* pData, XnUInt32 nSize) +{ + return m_pConnection->Send(pData, nSize); +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.h new file mode 100644 index 0000000..af36040 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputDataEndpoint.h @@ -0,0 +1,40 @@ +#ifndef __XNLINKOUTPUTDATAENDPOINT_H__ +#define __XNLINKOUTPUTDATAENDPOINT_H__ + +#include +#include + +namespace xn +{ + +class LinkOutputStreamsMgr; +class IConnectionFactory; +class IOutputConnection; + +class LinkOutputDataEndpoint +{ +public: + LinkOutputDataEndpoint(); + virtual ~LinkOutputDataEndpoint(); + + XnStatus Init(XnUInt16 nEndpointID, + IConnectionFactory* pConnectionFactory); + XnBool IsInitialized() const; + void Shutdown(); + XnStatus Connect(); + void Disconnect(); + XnBool IsConnected() const; + XnUInt16 GetMaxPacketSize() const; + + XnStatus SendData(const void* pData, XnUInt32 nSize); + +private: + IOutputConnection* m_pConnection; + XnBool m_bInitialized; + XnBool m_bConnected; + XnUInt16 m_nEndpointID; +}; + +} + +#endif // __XNLINKOUTPUTDATAENDPOINT_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.cpp new file mode 100644 index 0000000..450aa29 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.cpp @@ -0,0 +1,114 @@ +#include "XnLinkOutputStream.h" +#include "XnLinkMsgEncoder.h" +#include "XnLinkOutputDataEndpoint.h" +#include + +namespace xn +{ + +LinkOutputStream::LinkOutputStream() +{ + m_bInitialized = FALSE; + m_nStreamID = XN_LINK_STREAM_ID_INVALID; + m_compression = XN_LINK_COMPRESSION_NONE; + m_pLinkMsgEncoder = NULL; + m_pOutputDataEndpoint = NULL; + m_nPacketID = 0; +} + +LinkOutputStream::~LinkOutputStream() +{ + +} + +XnStatus LinkOutputStream::Init(XnUInt16 nStreamID, + XnUInt32 nMaxMsgSize, + XnUInt16 nMaxPacketSize, + XnLinkCompressionType compression, + XnUInt16 nInitialPacketID, + LinkOutputDataEndpoint* pOutputDataEndpoint) +{ + XnStatus nRetVal = XN_STATUS_OK; + XN_VALIDATE_INPUT_PTR(pOutputDataEndpoint); + + if (m_bInitialized) + { + //We shutdown first so we can re-initialize. + Shutdown(); + } + + m_nStreamID = nStreamID; + m_compression = compression; + m_nPacketID = nInitialPacketID; + m_pOutputDataEndpoint = pOutputDataEndpoint; + nRetVal = CreateLinkMsgEncoder(m_pLinkMsgEncoder); + XN_IS_STATUS_OK_LOG_ERROR("Create link msg encoder", nRetVal); + nRetVal = m_pLinkMsgEncoder->Init(nMaxMsgSize, nMaxPacketSize); + XN_IS_STATUS_OK_LOG_ERROR("Init link msg encoder", nRetVal); + m_bInitialized = TRUE; + return XN_STATUS_OK; +} + +XnBool LinkOutputStream::IsInitialized() const +{ + return m_bInitialized; +} + +void LinkOutputStream::Shutdown() +{ + m_pLinkMsgEncoder->Shutdown(); + XN_DELETE(m_pLinkMsgEncoder); + m_pLinkMsgEncoder = NULL; + m_nStreamID = XN_LINK_STREAM_ID_INVALID; + m_bInitialized = FALSE; +} + + +XnLinkCompressionType LinkOutputStream::GetCompression() const +{ + return m_compression; +} + +XnStatus LinkOutputStream::SendData(XnUInt16 nMsgType, + XnUInt16 nCID, + XnLinkFragmentation fragmentation, + const void* pData, + XnUInt32 nDataSize) const +{ + XnStatus nRetVal = XN_STATUS_OK; + m_pLinkMsgEncoder->BeginEncoding(nMsgType, m_nPacketID, m_nStreamID, + XnLinkFragmentation(fragmentation & XN_LINK_FRAG_BEGIN), nCID); + m_pLinkMsgEncoder->EncodeData(pData, nDataSize); + m_pLinkMsgEncoder->EndEncoding(XnLinkFragmentation(fragmentation & XN_LINK_FRAG_END)); + nRetVal = m_pOutputDataEndpoint->SendData(m_pLinkMsgEncoder->GetEncodedData(), + m_pLinkMsgEncoder->GetEncodedSize()); + XN_IS_STATUS_OK_LOG_ERROR("Send data in output data endpoint", nRetVal); + + // update packet ID + m_nPacketID = m_pLinkMsgEncoder->GetPacketID() + 1; + + return XN_STATUS_OK; +} + +XnStatus LinkOutputStream::CreateLinkMsgEncoder(LinkMsgEncoder*& pLinkMsgEncoder) +{ + switch (m_compression) + { + case XN_LINK_COMPRESSION_NONE: + { + pLinkMsgEncoder = XN_NEW(LinkMsgEncoder); + break; + } + default: + { + xnLogError(XN_MASK_LINK, "Unknown compression type: %u", m_compression); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + } + + XN_VALIDATE_ALLOC_PTR(pLinkMsgEncoder); + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.h new file mode 100644 index 0000000..1f04812 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStream.h @@ -0,0 +1,48 @@ +#ifndef __XNLINKOUTPUTSTREAM_H__ +#define __XNLINKOUTPUTSTREAM_H__ + +#include "ILinkOutputStream.h" +#include "XnLinkProtoUtils.h" + +namespace xn +{ + +class LinkMsgEncoder; + +class LinkOutputStream : public ILinkOutputStream +{ +public: + LinkOutputStream(); + virtual ~LinkOutputStream(); + + virtual XnStatus Init(XnUInt16 nStreamID, + XnUInt32 nMaxMsgSize, + XnUInt16 nMaxPacketSize, + XnLinkCompressionType compression, + XnUInt16 nInitialPacketID, + LinkOutputDataEndpoint* pOutputDataEndpoint); + + virtual XnBool IsInitialized() const; + virtual void Shutdown(); + virtual XnLinkCompressionType GetCompression() const; + + virtual XnStatus SendData(XnUInt16 nMsgType, + XnUInt16 nCID, + XnLinkFragmentation fragmentation, + const void* pData, + XnUInt32 nDataSize) const; + +protected: + virtual XnStatus CreateLinkMsgEncoder(LinkMsgEncoder*& pLinkMsgEncoder); + +private: + XnBool m_bInitialized; + XnUInt16 m_nStreamID; + XnLinkCompressionType m_compression; + LinkMsgEncoder* m_pLinkMsgEncoder; + LinkOutputDataEndpoint* m_pOutputDataEndpoint; + mutable XnUInt16 m_nPacketID; +}; +} + +#endif // __XNLINKOUTPUTSTREAM_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.cpp new file mode 100644 index 0000000..1c3cd2e --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.cpp @@ -0,0 +1,134 @@ +#include "XnLinkOutputStreamsMgr.h" +#include "ILinkOutputStream.h" +#include "XnLinkOutputStream.h" +#include + +namespace xn +{ + +const XnUInt16 LinkOutputStreamsMgr::INITIAL_PACKET_ID = 1; + +LinkOutputStreamsMgr::LinkOutputStreamsMgr() +{ +} + +LinkOutputStreamsMgr::~LinkOutputStreamsMgr() +{ + +} + +XnStatus LinkOutputStreamsMgr::Init() +{ + return XN_STATUS_OK; +} + +void LinkOutputStreamsMgr::Shutdown() +{ + for (XnUInt16 nStreamID = 0; nStreamID < m_outputStreams.GetSize(); nStreamID++) + { + ShutdownOutputStream(nStreamID); + } + m_outputStreams.Clear(); +} + +XnStatus LinkOutputStreamsMgr::InitOutputStream(XnUInt16 nStreamID, + XnUInt32 nMaxMsgSize, + XnUInt16 nMaxPacketSize, + XnLinkCompressionType compression, + XnStreamFragLevel streamFragLevel, + LinkOutputDataEndpoint* pOutputDataEndpoint) +{ + XnStatus nRetVal = XN_STATUS_OK; + ILinkOutputStream* pLinkOutputStream = NULL; + if (nStreamID < m_outputStreams.GetSize()) + { + XN_DELETE(m_outputStreams[nStreamID]); + m_outputStreams[nStreamID] = NULL; + } + + switch (streamFragLevel) + { + case XN_LINK_STREAM_FRAG_LEVEL_FRAMES: + pLinkOutputStream = XN_NEW(LinkOutputStream); + break; + default: + xnLogError(XN_MASK_LINK, "Bad stream fragmentation level %u", streamFragLevel); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + XN_VALIDATE_ALLOC_PTR(pLinkOutputStream); + + nRetVal = pLinkOutputStream->Init(nStreamID, + nMaxMsgSize, + nMaxPacketSize, + compression, + INITIAL_PACKET_ID, + pOutputDataEndpoint); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pLinkOutputStream); + xnLogError(XN_MASK_LINK, "Failed to initialize link output stream %u: %s", nStreamID, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return nRetVal; + } + + nRetVal = m_outputStreams.Set(nStreamID, pLinkOutputStream, NULL); + if (nRetVal != XN_STATUS_OK) + { + XN_DELETE(pLinkOutputStream); + xnLogError(XN_MASK_LINK, "Failed to add to output streams array: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + return nRetVal; + } + + return XN_STATUS_OK; +} + +void LinkOutputStreamsMgr::ShutdownOutputStream(XnUInt16 nStreamID) +{ + if (nStreamID > m_outputStreams.GetSize()) + { + xnLogWarning(XN_MASK_LINK, "Stream ID %u is not in array", nStreamID); + XN_ASSERT(FALSE); + return; + } + + if (m_outputStreams[nStreamID] != NULL) + { + m_outputStreams[nStreamID]->Shutdown(); + XN_DELETE(m_outputStreams[nStreamID]); + m_outputStreams[nStreamID] = NULL; + } +} + +XnStatus LinkOutputStreamsMgr::SendData(XnUInt16 nStreamID, + XnUInt16 nMsgType, + XnUInt16 nCID, + XnLinkFragmentation fragmentation, + const void* pData, + XnUInt32 nDataSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + if ((nStreamID >= m_outputStreams.GetSize()) || (m_outputStreams[nStreamID] == NULL) || + !m_outputStreams[nStreamID]->IsInitialized()) + { + xnLogError(XN_MASK_LINK, "Stream %u is not initialized", nStreamID); + XN_ASSERT(FALSE); + return XN_STATUS_NOT_INIT; + } + + nRetVal = m_outputStreams[nStreamID]->SendData(nMsgType, nCID, fragmentation, pData, nDataSize); + XN_IS_STATUS_OK_LOG_ERROR("Send data on output stream", nRetVal); + return XN_STATUS_OK; +} + +XnBool LinkOutputStreamsMgr::IsStreamInitialized( XnUInt16 nStreamID ) const +{ + return ( + nStreamID < m_outputStreams.GetSize() && + m_outputStreams[nStreamID] != NULL && + m_outputStreams[nStreamID]->IsInitialized()); +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.h new file mode 100644 index 0000000..0019f5f --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkOutputStreamsMgr.h @@ -0,0 +1,49 @@ +#ifndef __XNLINKOUTPUTSTREAMSMGR_H__ +#define __XNLINKOUTPUTSTREAMSMGR_H__ + +#include "XnLinkProtoLibDefs.h" +#include "XnLinkProtoUtils.h" +#include +#include +#include + +namespace xn +{ + +class ILinkMsgEncoderFactory; +class ILinkOutputStream; +class LinkOutputDataEndpoint; + +class LinkOutputStreamsMgr +{ +public: + LinkOutputStreamsMgr(); + ~LinkOutputStreamsMgr(); + XnStatus Init(); + void Shutdown(); + + XnStatus InitOutputStream(XnUInt16 nStreamID, + XnUInt32 nMaxMsgSize, + XnUInt16 nMaxPacketSize, + XnLinkCompressionType compression, + XnStreamFragLevel streamFragLevel, + LinkOutputDataEndpoint* pOutputDataEndpoint); + + XnBool IsStreamInitialized(XnUInt16 nStreamID) const; + + void ShutdownOutputStream(XnUInt16 nStreamID); + XnStatus SendData(XnUInt16 nStreamID, + XnUInt16 nMsgType, + XnUInt16 nCID, + XnLinkFragmentation fragmentation, + const void* pData, + XnUInt32 nDataSize); + +private: + static const XnUInt16 INITIAL_PACKET_ID; + xnl::Array m_outputStreams; +}; + +} + +#endif // __XNLINKOUTPUTSTREAMSMGR_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.cpp new file mode 100644 index 0000000..e9511ec --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.cpp @@ -0,0 +1,88 @@ +#include "XnLinkPacked10BitParser.h" +#include "XnLinkProtoUtils.h" +#include + +namespace xn +{ + +LinkPacked10BitParser::LinkPacked10BitParser() +{ + m_nState = 0; +} + +LinkPacked10BitParser::~LinkPacked10BitParser() +{ + +} + +XnStatus LinkPacked10BitParser::ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + //pDstWord always points to same address as pDst. + XnUInt16*& pDstWord = reinterpret_cast(pDst); + const XnUInt16* pDstWordEnd = reinterpret_cast(pDstEnd); + XnSizeT nPacketBits = 0; + XnSizeT nPacketDstWords = 0; + + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + + if ((fragmentation & XN_LINK_FRAG_BEGIN) != 0) + { + //Reset state for new frame + m_nState = 0; + } + + //Calculate needed space for this packet when it's unpacked + nPacketBits = (nPacketDataSize * 8); + nPacketDstWords = (nPacketBits / 10); + if (nPacketBits % 10 != 0) + { + nPacketDstWords++; + } + + if ((pDstWord + nPacketDstWords) > pDstWordEnd) //Do we have enough room for this packet? + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + while (pSrc < pSrcEnd) + { + switch (m_nState) + { + case 0: + *pDstWord = (*pSrc << 2); //8 first bits, make room for 2 more + m_nState++; + break; + case 1: + *pDstWord++ |= ((*pSrc >> 6) & 0x03); //2 more bits - got 1 whole word + *pDstWord = ((*pSrc & 0x3F) << 4); //6 first bits, make room for 4 more + m_nState++; + break; + case 2: + *pDstWord++ |= ((*pSrc >> 4) & 0x0F); //4 more bits - got 2 whole words + *pDstWord = ((*pSrc & 0x0F) << 6); //4 first bits, make room for 6 more + m_nState++; + break; + case 3: + *pDstWord++ |= ((*pSrc >> 2) & 0x3F); //6 more bits - got 3 whole words + *pDstWord = ((*pSrc & 0x03) << 8); //2 first bits, make room for 8 more + m_nState++; + break; + case 4: + *pDstWord++ |= *pSrc; //8 more bits - got 4 whole words + m_nState = 0; + break; + default: + XN_ASSERT(FALSE); + } + pSrc++; + } + + return XN_STATUS_OK; +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.h new file mode 100644 index 0000000..07a345f --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkPacked10BitParser.h @@ -0,0 +1,27 @@ +#ifndef __XNLINKPACKED10BITPARSER_H__ +#define __XNLINKPACKED10BITPARSER_H__ + +#include "XnLinkMsgParser.h" + +namespace xn +{ + +class LinkPacked10BitParser : public LinkMsgParser +{ +public: + LinkPacked10BitParser(); + virtual ~LinkPacked10BitParser(); + + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + +private: + XnUInt32 m_nState; +}; + +} + +#endif // __XNLINKPACKED10BITPARSER_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoLibDefs.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoLibDefs.h new file mode 100644 index 0000000..94fe46e --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoLibDefs.h @@ -0,0 +1,69 @@ +#ifndef __XNLINKPROTOLIBDEFS_H__ +#define __XNLINKPROTOLIBDEFS_H__ + +#include +#include + +#define XN_VENDOR_ID 0x1D27 +#define XN_VENDOR_PRIMESENSE "PrimeSense" + +//Max sizes +#define XN_EE_MAX_DEVICE_NAME 200 +#define XN_EE_MAX_STREAM_CREATION_INFO_LENGTH 80 +#define XN_EE_MAX_JOINTS 25 +#define XN_EE_MAX_BIST_TEST_NAME_LENGTH 32 +#define XN_EE_MAX_FILE_NAME_LENGTH 32 +#define XN_MAX_COMPONENT_NAME_LENGTH 32 +#define XN_MAX_VERSION_LENGTH 32 + +//Product IDs +#define XN_PRODUCT_ID_PS1250 0x1250 +#define XN_PRODUCT_ID_PS1260 0x1260 +#define XN_PRODUCT_ID_PS1270 0x1270 +#define XN_PRODUCT_ID_PS1290 0x1290 +#define XN_PRODUCT_ID_LENA 0x1280 + +//USB endpoint numbers +#define XN_EE_DEVICE_IN_DATA_BASE_ENDPOINT 0x81 + +//Control port numbers +#define XN_CONTROL_PORT_PS1200 20000 +#define XN_CONTROL_PORT_LENA 30000 + +#define XN_SERIAL_NUMBER_SIZE 32 + +typedef XnChar XnConnectionString[XN_FILE_MAX_PATH]; + +typedef enum XnTransportType +{ + XN_TRANSPORT_TYPE_NONE = 0, + XN_TRANSPORT_TYPE_USB = 1, + XN_TRANSPORT_TYPE_SOCKETS = 2 +} XnTransportType; + +#define XN_FORMAT_PASS_THROUGH_UNPACK (OniPixelFormat)0 +#define XN_FORMAT_PASS_THROUGH_RAW (OniPixelFormat)1 + +typedef XnUInt32 XnStreamFragLevel; +typedef XnUInt32 XnStreamType; + +typedef struct XnAvailableGesture +{ + const XnChar* m_strGesture; + XnBool m_bProgressSupported; + XnBool m_bCurrentlyActive; +} XnAvailableGesture; + +typedef struct XnLeanVersion +{ + XnUInt8 m_nMajor; + XnUInt8 m_nMinor; +} XnLeanVersion; + +typedef struct XnComponentVersion +{ + XnChar m_strName[XN_MAX_COMPONENT_NAME_LENGTH]; + XnChar m_strVersion[XN_MAX_VERSION_LENGTH]; +} XnComponentVersion; + +#endif // __XNLINKPROTOLIBDEFS_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.cpp new file mode 100644 index 0000000..13737a3 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.cpp @@ -0,0 +1,1160 @@ +#include "XnLinkProtoUtils.h" +#include "XnLinkProto.h" +#include "XnLinkDefs.h" +#include "XnLinkStatusCodes.h" +#include +#include +#include +#include + +XnStatus xn::LinkPacketHeader::Validate(XnUInt32 nBytesToRead) const +{ + //First of all validate minimum header size + if (nBytesToRead < sizeof(LinkPacketHeader)) + { + xnLogError(XN_MASK_LINK, "Not enough data left to read - got only %u bytes, but link packet header is %u bytes", nBytesToRead, sizeof(LinkPacketHeader)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_HEADER_SIZE; + } + + //Validate Magic + if (!IsMagicValid()) + { + XnChar strData[256] = ""; + for (XnUInt32 i = 0; i < XN_MIN(nBytesToRead, 10); ++i) + { + XnChar s[10]; + sprintf(s, "%02X ", ((XnUInt8*)this)[i]); + xnOSStrAppend(strData, s, sizeof(strData)); + } + xnLogError(XN_MASK_LINK, "Got bad packet magic. size: %u. Beginning of packet data was: %s", nBytesToRead, strData); +// XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_MAGIC; + } + + //Validate packet size + if (nBytesToRead < GetSize()) + { + xnLogError(XN_MASK_LINK, "Got partial packet - only %u bytes remaining", nBytesToRead); +// XN_ASSERT(FALSE); + return XN_STATUS_LINK_PARTIAL_PACKET; + } + + //We passed all validations :) + return XN_STATUS_OK; +} + +XnStatus xnLinkResponseCodeToStatus(XnUInt16 nResponseCode) +{ + switch (nResponseCode) + { + case XN_LINK_RESPONSE_OK: + return XN_STATUS_OK; + case XN_LINK_RESPONSE_PENDING: + return XN_STATUS_LINK_RESP_PENDING; + case XN_LINK_RESPONSE_BAD_FILE_TYPE: + return XN_STATUS_LINK_RESP_BAD_FILE_TYPE; + case XN_LINK_RESPONSE_CMD_ERROR: + return XN_STATUS_LINK_RESP_CMD_ERROR; + case XN_LINK_RESPONSE_CMD_NOT_SUPPORTED: + return XN_STATUS_LINK_RESP_CMD_NOT_SUPPORTED; + case XN_LINK_RESPONSE_BAD_CMD_SIZE: + return XN_STATUS_LINK_RESP_BAD_CMD_SIZE; + case XN_LINK_RESPONSE_BAD_PARAMETERS: + return XN_STATUS_LINK_RESP_BAD_PARAMETERS; + case XN_LINK_RESPONSE_CORRUPT_PACKET: + return XN_STATUS_LINK_RESP_CORRUPT_PACKET; + default: + return XN_STATUS_LINK_RESP_UNKNOWN; + } +} + +const XnChar* xnLinkResponseCodeToStr(XnUInt16 nResponseCode) +{ + return xnGetStatusString(xnLinkResponseCodeToStatus(nResponseCode)); +} + +const XnChar* xnFragmentationFlagsToStr(XnLinkFragmentation fragmentation) +{ + switch (fragmentation) + { + case XN_LINK_FRAG_MIDDLE: + return "MIDDLE"; + case XN_LINK_FRAG_BEGIN: + return "BEGIN"; + case XN_LINK_FRAG_END: + return "END"; + case XN_LINK_FRAG_SINGLE: + return "SINGLE"; + default: + XN_ASSERT(FALSE); + return NULL; + } +} + +const XnChar* xnLinkStreamTypeToString(XnStreamType streamType) +{ + switch (streamType) + { + case XN_LINK_STREAM_TYPE_COLOR: + return "Image"; + case XN_LINK_STREAM_TYPE_IR: + return "IR"; + case XN_LINK_STREAM_TYPE_SHIFTS: + return "Depth"; + case XN_LINK_STREAM_TYPE_AUDIO: + return "Audio"; + case XN_LINK_STREAM_TYPE_LOG: + return "Log"; + case XN_LINK_STREAM_TYPE_USER: + return "User"; + case XN_LINK_STREAM_TYPE_HANDS: + return "Hands"; + case XN_LINK_STREAM_TYPE_GESTURES: + return "Gestures"; + case XN_LINK_STREAM_TYPE_DY: + return "DY"; + default: + return "Unknown"; + } +} + +XnStreamType xnLinkStreamTypeFromString(const XnChar* strType) +{ + if (xnOSStrCaseCmp(strType, "Image") == 0) + { + return XN_LINK_STREAM_TYPE_COLOR; + } + else if (xnOSStrCaseCmp(strType, "IR") == 0) + { + return XN_LINK_STREAM_TYPE_IR; + } + else if (xnOSStrCaseCmp(strType, "Depth") == 0) + { + return XN_LINK_STREAM_TYPE_SHIFTS; + } + else if (xnOSStrCaseCmp(strType, "Audio") == 0) + { + return XN_LINK_STREAM_TYPE_AUDIO; + } + else if (xnOSStrCaseCmp(strType, "Log") == 0) + { + return XN_LINK_STREAM_TYPE_LOG; + } + else if (xnOSStrCaseCmp(strType, "User") == 0) + { + return XN_LINK_STREAM_TYPE_USER; + } + else if (xnOSStrCaseCmp(strType, "Hands") == 0) + { + return XN_LINK_STREAM_TYPE_HANDS; + } + else if (xnOSStrCaseCmp(strType, "Gestures") == 0) + { + return XN_LINK_STREAM_TYPE_GESTURES; + } + else if (xnOSStrCaseCmp(strType, "DY") == 0) + { + return XN_LINK_STREAM_TYPE_DY; + } + else + { + return XN_LINK_STREAM_TYPE_INVALID; + } +} + +#define XN_GESTURE_NAME_RAISE_HAND "RaiseHand" +#define XN_GESTURE_NAME_WAVE "Wave" +#define XN_GESTURE_NAME_CLICK "Click" +#define XN_GESTURE_NAME_MOVING_HAND "MovingHand" + +const XnChar* xnLinkGestureTypeToName(XnUInt32 gestureType) +{ + switch (gestureType) + { + case XN_LINK_GESTURE_RAISE_HAND: + return XN_GESTURE_NAME_RAISE_HAND; + case XN_LINK_GESTURE_WAVE: + return XN_GESTURE_NAME_WAVE; + case XN_LINK_GESTURE_CLICK: + return XN_GESTURE_NAME_CLICK; + case XN_LINK_GESTURE_MOVING_HAND: + return XN_GESTURE_NAME_MOVING_HAND; + default: + xnLogError(XN_MASK_LINK, "Unknown gesture: %d", gestureType); + XN_ASSERT(FALSE); + return NULL; + } +} + +XnUInt32 xnLinkGestureNameToType(const XnChar* strGesture) +{ + if (strcmp(strGesture, XN_GESTURE_NAME_RAISE_HAND) == 0) + return XN_LINK_GESTURE_RAISE_HAND; + else if (strcmp(strGesture, XN_GESTURE_NAME_WAVE) == 0) + return XN_LINK_GESTURE_WAVE; + else if (strcmp(strGesture, XN_GESTURE_NAME_CLICK) == 0) + return XN_LINK_GESTURE_CLICK; + else if (strcmp(strGesture, XN_GESTURE_NAME_MOVING_HAND) == 0) + return XN_LINK_GESTURE_MOVING_HAND; + + xnLogError(XN_MASK_LINK, "Unknown gesture: %s", strGesture); + XN_ASSERT(FALSE); + return XN_PREPARE_VAR32_IN_BUFFER(XN_LINK_GESTURE_NONE); +} + +#define XN_POSE_NAME_PSI "Psi" + +const XnChar* xnLinkPoseTypeToName(XnUInt32 poseType) +{ + switch (poseType) + { + case XN_LINK_POSE_TYPE_PSI: + return XN_POSE_NAME_PSI; + case XN_LINK_POSE_TYPE_NONE: + return NULL; + default: + xnLogError(XN_MASK_LINK, "Unknown pose: %d", poseType); + XN_ASSERT(FALSE); + return NULL; + } +} + +XnUInt32 xnLinkPoseNameToType(const XnChar* strPose) +{ + if (strPose == NULL) + return XN_LINK_POSE_TYPE_NONE; + else if (strcmp(strPose, XN_POSE_NAME_PSI) == 0) + return XN_LINK_POSE_TYPE_PSI; + + xnLogError(XN_MASK_LINK, "Unknown pose: %s", strPose); + XN_ASSERT(FALSE); + return XN_LINK_GESTURE_NONE; +} + +XnStatus xnLinkPosesToNames(XnUInt32 nPoses, xnl::Array& aPosesNames) +{ + XnStatus nRetVal = XN_STATUS_OK; + + aPosesNames.Clear(); + + XnUInt32 shifted = 0; + while (nPoses != 0) + { + if ((nPoses & 0x01) != 0) + { + const XnChar* strPose = xnLinkPoseTypeToName(1 << shifted); + if (strPose == NULL) + { + return XN_STATUS_LINK_UNKNOWN_POSE; + } + + nRetVal = aPosesNames.AddLast(strPose); + XN_IS_STATUS_OK(nRetVal); + } + + shifted++; + nPoses >>= 1; + } + + return (XN_STATUS_OK); +} + +xnl::Point3D xnLinkPoint3DToPoint3D(const XnLinkPoint3D& point) +{ + xnl::Point3D result; + result.x = XN_PREPARE_VAR_FLOAT_IN_BUFFER(point.m_fX); + result.y = XN_PREPARE_VAR_FLOAT_IN_BUFFER(point.m_fY); + result.z = XN_PREPARE_VAR_FLOAT_IN_BUFFER(point.m_fZ); + return result; +} + +XnLinkPoint3D XnPoint3DToLinkPoint3D(const xnl::Point3D& point) +{ + XnLinkPoint3D result; + result.m_fX = XN_PREPARE_VAR_FLOAT_IN_BUFFER(point.x); + result.m_fY = XN_PREPARE_VAR_FLOAT_IN_BUFFER(point.y); + result.m_fZ = XN_PREPARE_VAR_FLOAT_IN_BUFFER(point.z); + return result; +} + +XnLinkBoundingBox3D xnBoundingBox3DToLinkBoundingBox3D(const xnl::Box3D& box) +{ + XnLinkBoundingBox3D result; + result.leftBottomNear = XnPoint3DToLinkPoint3D(box.m_bottomLeftNear); + result.rightTopFar = XnPoint3DToLinkPoint3D(box.m_topRightFar); + return result; +} + +xnl::Box3D xnLinkBoundingBox3DToBoundingBox3D(const XnLinkBoundingBox3D& box) +{ + xnl::Box3D result; + + result.m_bottomLeftNear = xnLinkPoint3DToPoint3D(box.leftBottomNear); + result.m_topRightFar = xnLinkPoint3DToPoint3D(box.rightTopFar); + + return result; +} + +XnStatus xnLinkGetStreamDumpName(XnUInt16 nStreamID, XnChar* strDumpName, XnUInt32 nDumpNameSize) +{ + XnUInt32 nCharsWritten = 0; + return xnOSStrFormat(strDumpName, nDumpNameSize, &nCharsWritten, "Stream.%05u.In.raw", nStreamID); +} + +XnStatus xnLinkGetEPDumpName(XnUInt16 nEPID, XnChar* strDumpName, XnUInt32 nDumpNameSize) +{ + XnUInt32 nCharsWritten = 0; + return xnOSStrFormat(strDumpName, nDumpNameSize, &nCharsWritten, "EP.%05u.In", nEPID); +} + +XnStatus xnLinkParseIDSet(xnl::Array& idSet, const void* pLinkIDSet, XnUInt32 nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + const XnUInt8* pSource = reinterpret_cast(pLinkIDSet); + const XnUInt8* pNextSource = NULL; + const XnUInt8* pSourceEnd = pSource + nSize; + const XnLinkIDSetHeader* pHeader = reinterpret_cast(pSource); + const XnLinkIDSetGroup* pLinkIDBitSet = NULL; + XnUInt8 nGroupID = 0; + XnUInt32 nGroupSize = 0; + + if (nSize < sizeof(*pHeader)) + { + XN_ASSERT(FALSE); + return XN_STATUS_INPUT_BUFFER_OVERFLOW; + } + + if (pHeader->m_nFormat != XN_PREPARE_VAR32_IN_BUFFER(XN_LINK_ID_SET_FORMAT_BITSET)) + { + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + pSource += sizeof(*pHeader); + + while (pSource < pSourceEnd) + { + pLinkIDBitSet = reinterpret_cast(pSource); + nGroupID = pLinkIDBitSet->m_header.m_nGroupID; + nRetVal = idSet.SetMinSize(nGroupID + 1); + XN_IS_STATUS_OK(nRetVal); + nGroupSize = pLinkIDBitSet->m_header.m_nSize; + pNextSource = pSource + nGroupSize; + if (pNextSource > pSourceEnd) + { + XN_ASSERT(FALSE); + return XN_STATUS_INPUT_BUFFER_OVERFLOW; + } + nRetVal = idSet[nGroupID].SetData(pLinkIDBitSet->m_idsBitmap, nGroupSize - sizeof(pLinkIDBitSet->m_header)); + XN_IS_STATUS_OK(nRetVal); + pSource = pNextSource; + } + + return XN_STATUS_OK; +} + +XnStatus xnLinkEncodeIDSet(void* pIDSet, XnUInt32 *pnEncodedSize, const XnUInt16* pIDs, XnUInt32 nNumIDs) +{ + XnUInt8 nGroupID = 0xFF; + XnUInt8 nNewGroupID = 0xFF; + const XnUInt16* pMsgType = pIDs; + const XnUInt16* pMsgTypesEnd = pIDs + nNumIDs; + XnUInt32 nMaxEncodedSize = *pnEncodedSize; + XnUInt32 nMsgTypeLow = 0; + XnUInt32 nByteIndex = 0; + XnLinkIDSetHeader* pHeader = (XnLinkIDSetHeader*)pIDSet; + XnLinkIDSetGroup* pIDSetGroup = (XnLinkIDSetGroup*)((XnUInt8*)pIDSet + sizeof(*pHeader)); + XnUInt8* pIDSetEnd = (XnUInt8*)pIDSet + nMaxEncodedSize; + XnUInt8 nGroupSize = 0; + XnUInt16 nNumGroups = 0; + + if (nMaxEncodedSize < sizeof(*pHeader)) + { + //Not enough room to encode header + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + //First reset entire output buffer + memset(pIDSet, 0, nMaxEncodedSize); + + pHeader->m_nFormat = XN_PREPARE_VAR16_IN_BUFFER(XN_LINK_ID_SET_FORMAT_BITSET); + + while (pMsgType < pMsgTypesEnd) + { + nNewGroupID = (*pMsgType >> 8); + //We already know the group number, now take only the low 8 bits of msg type + nMsgTypeLow = (*pMsgType & 0xFF); + nByteIndex = (nMsgTypeLow >> 3); + + if (nNewGroupID != nGroupID) + { + //Encountered a new group ID + nNumGroups++; + //Advance group pointer to next group + pIDSetGroup = (XnLinkIDSetGroup*)((XnUInt8*)pIDSetGroup + pIDSetGroup->m_header.m_nSize); + if (((XnUInt8*)pIDSetGroup + sizeof(pIDSetGroup->m_header)) > pIDSetEnd) + { + //Not enough room in output buffer to encode this group's header + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + pIDSetGroup->m_header.m_nGroupID = nNewGroupID; + pIDSetGroup->m_header.m_nSize = sizeof(pIDSetGroup->m_header); //Size starts with size of header + nGroupID = nNewGroupID; + } + + if (((XnUInt8*)pIDSetGroup + sizeof(pIDSetGroup->m_header) + nByteIndex) > pIDSetEnd) + { + //Not enough room to add a bit for this msg type + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + nGroupSize = (sizeof(pIDSetGroup->m_header) + (XnUInt8)nByteIndex + 1); + if (nGroupSize > pIDSetGroup->m_header.m_nSize) + { + //Update size of this group + pIDSetGroup->m_header.m_nSize = nGroupSize; + } + + //We OR the value in the array with a mask that contains a 1 bit only for this msg type. + pIDSetGroup->m_idsBitmap[nByteIndex] |= (1 << ((~nMsgTypeLow) & 0x07)); + pMsgType++; + } + + pHeader->m_nNumGroups = XN_PREPARE_VAR16_IN_BUFFER(nNumGroups); + *pnEncodedSize = XnUInt32((XnUInt8*)pIDSetGroup + pIDSetGroup->m_header.m_nSize - (XnUInt8*)pIDSet); + + return XN_STATUS_OK; //Success +} + +XnStatus xnLinkParseFrameSyncStreamIDs(xnl::Array& frameSyncStreamIDs, const void* pFrameSyncStreamIDs, XnUInt32 nBufferSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + const XnLinkFrameSyncStreamIDs* pLinkFrameSyncStreamIDs = reinterpret_cast(pFrameSyncStreamIDs); + XnUInt16 nNumStreamIDs = XN_PREPARE_VAR16_IN_BUFFER(pLinkFrameSyncStreamIDs->m_nNumStreamIDs); + if (nBufferSize < sizeof(pLinkFrameSyncStreamIDs->m_nNumStreamIDs) + (sizeof(pLinkFrameSyncStreamIDs->m_anStreamIDs[0]) * nNumStreamIDs)) + { + return XN_STATUS_INPUT_BUFFER_OVERFLOW; + } + + nRetVal = frameSyncStreamIDs.SetSize(nNumStreamIDs); + XN_IS_STATUS_OK(nRetVal); + for (XnUInt16 i = 0; i < nNumStreamIDs; i++) + { + frameSyncStreamIDs[i] = XN_PREPARE_VAR16_IN_BUFFER(pLinkFrameSyncStreamIDs->m_anStreamIDs[i]); + } + + return XN_STATUS_OK; +} + +XnStatus xnLinkEncodeFrameSyncStreamIDs(void* pFrameSyncStreamIDs, XnUInt32& nBufferSize, const xnl::Array& frameSyncStreamIDs) +{ + XnLinkFrameSyncStreamIDs* pLinkFrameSyncStreamIDs = reinterpret_cast(pFrameSyncStreamIDs); + + if (nBufferSize < (sizeof(pLinkFrameSyncStreamIDs->m_anStreamIDs) + (sizeof(pLinkFrameSyncStreamIDs->m_anStreamIDs[0]) * frameSyncStreamIDs.GetSize()))) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + pLinkFrameSyncStreamIDs->m_nNumStreamIDs = XN_PREPARE_VAR16_IN_BUFFER(XnUInt16(frameSyncStreamIDs.GetSize())); + + for (XnUInt32 i = 0; i < frameSyncStreamIDs.GetSize(); i++) + { + pLinkFrameSyncStreamIDs->m_anStreamIDs[i] = XN_PREPARE_VAR16_IN_BUFFER(frameSyncStreamIDs[i]); + } + + return XN_STATUS_OK; +} + +XnStatus xnLinkParseComponentVersionsList(xnl::Array& componentVersions, const XnLinkComponentVersionsList* pLinkList, XnUInt32 nBufferSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (nBufferSize < sizeof(pLinkList->m_nCount)) + { + xnLogError(XN_MASK_LINK, "Components versions list size should be at least %u bytes, but got %u bytes.", + sizeof(pLinkList->m_nCount), nBufferSize); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + XnUInt32 nCount = XN_PREPARE_VAR32_IN_BUFFER(pLinkList->m_nCount); + + XnUInt32 nExpectedSize = + sizeof(pLinkList->m_nCount) + + (sizeof(pLinkList->m_components[0]) * nCount); + + if (nBufferSize != nExpectedSize) + { + xnLogError(XN_MASK_LINK, "Got bad size of 'components versions list' property: %u instead of %u", nBufferSize, nExpectedSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + nRetVal = componentVersions.SetSize(nCount); + XN_IS_STATUS_OK_LOG_ERROR("Set size of output supported map output modes array", nRetVal); + for (XnUInt32 i = 0; i < nCount; i++) + { + nRetVal = xnOSStrCopy(componentVersions[i].m_strName, pLinkList->m_components[i].m_strName, sizeof(componentVersions[i].m_strName)); + XN_IS_STATUS_OK(nRetVal); + nRetVal = xnOSStrCopy(componentVersions[i].m_strVersion, pLinkList->m_components[i].m_strVersion, sizeof(componentVersions[i].m_strVersion)); + XN_IS_STATUS_OK(nRetVal); + } + + return XN_STATUS_OK; +} + +/*static struct +{ + const XnChar* m_strCapabilityName; + XnUInt8 m_nInterfaceID; +} CAP_NAME_TO_LINK_INTERFACE_ID[] = { + {XN_CAPABILITY_MIRROR, XN_LINK_INTERFACE_MIRROR}, + {XN_CAPABILITY_ALTERNATIVE_VIEW_POINT, XN_LINK_INTERFACE_ALTERNATIVE_VIEW_POINT}, + {XN_CAPABILITY_CROPPING, XN_LINK_INTERFACE_CROPPING}, + {XN_CAPABILITY_USER_POSITION, XN_LINK_INTERFACE_USER_POSITION}, + {XN_CAPABILITY_SKELETON, XN_LINK_INTERFACE_SKELETON}, + {XN_CAPABILITY_POSE_DETECTION, XN_LINK_INTERFACE_POSE_DETECTION}, + {XN_CAPABILITY_LOCK_AWARE, XN_LINK_INTERFACE_LOCK_AWARE}, + {XN_CAPABILITY_ERROR_STATE, XN_LINK_INTERFACE_ERROR_STATE}, + {XN_CAPABILITY_FRAME_SYNC, XN_LINK_INTERFACE_FRAME_SYNC}, + {XN_CAPABILITY_DEVICE_IDENTIFICATION, XN_LINK_INTERFACE_DEVICE_IDENTIFICATION}, + {XN_CAPABILITY_BRIGHTNESS, XN_LINK_INTERFACE_BRIGHTNESS}, + {XN_CAPABILITY_CONTRAST, XN_LINK_INTERFACE_CONTRAST}, + {XN_CAPABILITY_HUE, XN_LINK_INTERFACE_HUE}, + {XN_CAPABILITY_SATURATION, XN_LINK_INTERFACE_SATURATION}, + {XN_CAPABILITY_SHARPNESS, XN_LINK_INTERFACE_SHARPNESS}, + {XN_CAPABILITY_GAMMA, XN_LINK_INTERFACE_GAMMA}, + {XN_CAPABILITY_COLOR_TEMPERATURE, XN_LINK_INTERFACE_COLOR_TEMPERATURE}, + {XN_CAPABILITY_BACKLIGHT_COMPENSATION, XN_LINK_INTERFACE_BACKLIGHT_COMPENSATION}, + {XN_CAPABILITY_GAIN, XN_LINK_INTERFACE_GAIN}, + {XN_CAPABILITY_PAN, XN_LINK_INTERFACE_PAN}, + {XN_CAPABILITY_TILT, XN_LINK_INTERFACE_TILT}, + {XN_CAPABILITY_ROLL, XN_LINK_INTERFACE_ROLL}, + {XN_CAPABILITY_ZOOM, XN_LINK_INTERFACE_ZOOM}, + {XN_CAPABILITY_EXPOSURE, XN_LINK_INTERFACE_EXPOSURE}, + {XN_CAPABILITY_IRIS, XN_LINK_INTERFACE_IRIS}, + {XN_CAPABILITY_FOCUS, XN_LINK_INTERFACE_FOCUS}, + {XN_CAPABILITY_LOW_LIGHT_COMPENSATION, XN_LINK_INTERFACE_LOW_LIGHT_COMPENSATION}, + {XN_CAPABILITY_ANTI_FLICKER, XN_LINK_INTERFACE_ANTI_FLICKER}, + {XN_CAPABILITY_HAND_TOUCHING_FOV_EDGE, XN_LINK_INTERFACE_HAND_TOUCHING_FOV_EDGE}, +}; + +XnUInt8 xnLinkNICapabilityToInterfaceID(const XnChar* strCapabilityName) +{ + for (XnUInt32 i = 0; i < sizeof(CAP_NAME_TO_LINK_INTERFACE_ID) / sizeof(CAP_NAME_TO_LINK_INTERFACE_ID[0]); i++) + { + if (xnOSStrCmp(strCapabilityName, CAP_NAME_TO_LINK_INTERFACE_ID[i].m_strCapabilityName) == 0) + { + return CAP_NAME_TO_LINK_INTERFACE_ID[i].m_nInterfaceID; + } + } + + return XN_LINK_INTERFACE_INVALID; +} + +const XnChar* xnLinkInterfaceIDToNICapability(XnUInt8 nInterfaceID) +{ + for (XnUInt32 i = 0; i < sizeof(CAP_NAME_TO_LINK_INTERFACE_ID) / sizeof(CAP_NAME_TO_LINK_INTERFACE_ID[0]); i++) + { + if (nInterfaceID == CAP_NAME_TO_LINK_INTERFACE_ID[i].m_nInterfaceID) + { + return CAP_NAME_TO_LINK_INTERFACE_ID[i].m_strCapabilityName; + } + } + + return NULL; +} +*/ + +const XnChar* xnLinkPropTypeToStr(XnLinkPropType propType) +{ + static const XnChar* PROP_TYPE_STRS[] = + { + "None", //0x0000 + "Int", //0x0001 + "Real", //0x0002 + "String", //0x0003 + "General", //0x0004 + }; + + return (propType < sizeof(PROP_TYPE_STRS) / sizeof(PROP_TYPE_STRS[0])) ? PROP_TYPE_STRS[propType] : "Unknown"; +} + +/*XnProductionNodeType xnLinkStreamTypeToNINodeType(XnLinkStreamType streamType) +{ + switch (streamType) + { + case XN_LINK_STREAM_TYPE_COLOR: + return XN_NODE_TYPE_IMAGE; + case XN_LINK_STREAM_TYPE_IR: + return XN_NODE_TYPE_IR; + case XN_LINK_STREAM_TYPE_SHIFTS: + return XN_NODE_TYPE_DEPTH; + case XN_LINK_STREAM_TYPE_AUDIO: + return XN_NODE_TYPE_AUDIO; + case XN_LINK_STREAM_TYPE_USER: + return XN_NODE_TYPE_USER; + case XN_LINK_STREAM_TYPE_HANDS: + return XN_NODE_TYPE_HANDS; + case XN_LINK_STREAM_TYPE_GESTURES: + return XN_NODE_TYPE_GESTURE; + case XN_LINK_STREAM_TYPE_NONE: + case XN_LINK_STREAM_TYPE_INVALID: + default: + return XN_NODE_TYPE_INVALID; + } +} + +XnLinkStreamType xnLinkNINodeTypeToStreamType(XnProductionNodeType nodeType) +{ + switch (nodeType) + { + case XN_NODE_TYPE_DEPTH: + return XN_LINK_STREAM_TYPE_SHIFTS; + case XN_NODE_TYPE_IMAGE: + return XN_LINK_STREAM_TYPE_COLOR; + case XN_NODE_TYPE_AUDIO: + return XN_LINK_STREAM_TYPE_AUDIO; + case XN_NODE_TYPE_IR: + return XN_LINK_STREAM_TYPE_IR; + case XN_NODE_TYPE_USER: + return XN_LINK_STREAM_TYPE_USER; + case XN_NODE_TYPE_HANDS: + return XN_LINK_STREAM_TYPE_HANDS; + case XN_NODE_TYPE_GESTURE: + return XN_LINK_STREAM_TYPE_GESTURES; + case XN_NODE_TYPE_INVALID: + default: + return XN_LINK_STREAM_TYPE_INVALID; + } +}*/ + +void xnLinkParseDetailedVersion(XnLinkDetailedVersion& version, const XnLinkDetailedVersion& linkVersion) +{ + version.m_nMajor = linkVersion.m_nMajor; + version.m_nMinor = linkVersion.m_nMinor; + version.m_nMaintenance = XN_PREPARE_VAR16_IN_BUFFER(linkVersion.m_nMaintenance); + version.m_nBuild = XN_PREPARE_VAR32_IN_BUFFER(linkVersion.m_nBuild); + xnOSMemCopy(version.m_strModifier, linkVersion.m_strModifier, sizeof(version.m_strModifier)); +} + +void xnLinkParseLeanVersion(XnLeanVersion& version, const XnLinkLeanVersion& linkVersion) +{ + version.m_nMajor = linkVersion.m_nMajor; + version.m_nMinor = linkVersion.m_nMinor; +} + +void xnEncodeLeanVersion(XnLinkLeanVersion& linkVersion, const XnLeanVersion& version) +{ + linkVersion.m_nMajor = version.m_nMajor; + linkVersion.m_nMinor = version.m_nMinor; +} + +void xnLinkParseVideoMode(XnFwStreamVideoMode& videoMode, const XnLinkVideoMode& linkVideoMode) +{ + videoMode.m_nXRes = XN_PREPARE_VAR16_IN_BUFFER(linkVideoMode.m_nXRes); + videoMode.m_nYRes = XN_PREPARE_VAR16_IN_BUFFER(linkVideoMode.m_nYRes); + videoMode.m_nFPS = XN_PREPARE_VAR16_IN_BUFFER(linkVideoMode.m_nFPS); + videoMode.m_nPixelFormat = (XnFwPixelFormat)XN_PREPARE_VAR16_IN_BUFFER(linkVideoMode.m_nPixelFormat); + videoMode.m_nCompression = (XnFwCompressionType)XN_PREPARE_VAR16_IN_BUFFER(linkVideoMode.m_nCompression); +} + +void xnLinkEncodeVideoMode(XnLinkVideoMode& linkVideoMode, const XnFwStreamVideoMode& videoMode) +{ + linkVideoMode.m_nXRes = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)videoMode.m_nXRes); + linkVideoMode.m_nYRes = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)videoMode.m_nYRes); + linkVideoMode.m_nFPS = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)videoMode.m_nFPS); + linkVideoMode.m_nPixelFormat = (XnUInt8)videoMode.m_nPixelFormat; + linkVideoMode.m_nCompression = (XnUInt8)videoMode.m_nCompression; +} + +XnStatus xnLinkParseSupportedVideoModes(xnl::Array& aModes, + const XnLinkSupportedVideoModes* pLinkSupportedModes, + XnUInt32 nBufferSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nModes = 0; + XnUInt32 nExpectedSize = 0; + + XN_VALIDATE_INPUT_PTR(pLinkSupportedModes); + + if (nBufferSize < sizeof(pLinkSupportedModes->m_nNumModes)) + { + xnLogError(XN_MASK_LINK, "Size of link video modes was only %u bytes, must be at least %u.", nBufferSize, + sizeof(pLinkSupportedModes->m_nNumModes)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + nModes = XN_PREPARE_VAR32_IN_BUFFER(pLinkSupportedModes->m_nNumModes); + nExpectedSize = (sizeof(pLinkSupportedModes->m_nNumModes) + + (sizeof(pLinkSupportedModes->m_supportedVideoModes[0]) * nModes)); + if (nBufferSize != nExpectedSize) + { + xnLogError(XN_MASK_LINK, "Got bad size of 'supported video modes' property: %u instead of %u", nBufferSize, nExpectedSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = aModes.SetSize(nModes); + XN_IS_STATUS_OK_LOG_ERROR("Set size of output supported map output modes array", nRetVal); + for (XnUInt32 i = 0; i < nModes; i++) + { + xnLinkParseVideoMode(aModes[i], pLinkSupportedModes->m_supportedVideoModes[i]); + } + + return XN_STATUS_OK; +} + +XnStatus xnLinkParseBitSet(xnl::BitSet& bitSet, const XnLinkBitSet* pLinkBitSet, XnUInt32 nBufferSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nExpectedSize = 0; + XnUInt32 nBitSetSize = 0; + XN_VALIDATE_INPUT_PTR(pLinkBitSet); + + if (nBufferSize < sizeof(pLinkBitSet->m_nSize)) + { + xnLogError(XN_MASK_LINK, "Size of link bit set was only %u bytes, must be at least %u", nBufferSize, + sizeof(pLinkBitSet->m_nSize)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + nBitSetSize = XN_PREPARE_VAR32_IN_BUFFER(pLinkBitSet->m_nSize); + nExpectedSize = sizeof(pLinkBitSet->m_aData[0]) * nBitSetSize; + if (nExpectedSize != nBitSetSize) + { + xnLogError(XN_MASK_LINK, "Expected size of bitset to be %u bytes, but got %u", nExpectedSize, nBitSetSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + nRetVal = bitSet.SetData(pLinkBitSet->m_aData, nBitSetSize); + XN_IS_STATUS_OK_LOG_ERROR("Set data bytes in bit set", nRetVal); + return XN_STATUS_OK; +} + +XnStatus xnLinkEncodeBitSet(XnLinkBitSet& linkBitSet, XnUInt32& nBufferSize, const xnl::BitSet& bitSet) +{ + XnUInt32 nBytes = (bitSet.GetSize() >> 3); + XnUInt32 nPartial = (bitSet.GetSize()%4)?1:0; + XnUInt32 nDataSize = (nBytes + nPartial)<<2; + + XnUInt32 nRequiredBufferSize = nDataSize + sizeof(linkBitSet.m_nSize); + if (nBufferSize < nRequiredBufferSize) + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + linkBitSet.m_nSize = XN_PREPARE_VAR32_IN_BUFFER(nDataSize); + xnOSMemCopy(linkBitSet.m_aData, bitSet.GetData(), nDataSize); + nBufferSize = nRequiredBufferSize; + + return (XN_STATUS_OK); +} + +void xnLinkParseShiftToDepthConfig(XnShiftToDepthConfig& shiftToDepthConfig, const XnLinkShiftToDepthConfig& linkShiftToDepthConfig) +{ + shiftToDepthConfig.nZeroPlaneDistance = XN_PREPARE_VAR16_IN_BUFFER(linkShiftToDepthConfig.nZeroPlaneDistance); + shiftToDepthConfig.fZeroPlanePixelSize = XN_PREPARE_VAR_FLOAT_IN_BUFFER(linkShiftToDepthConfig.fZeroPlanePixelSize); + shiftToDepthConfig.fEmitterDCmosDistance = XN_PREPARE_VAR_FLOAT_IN_BUFFER(linkShiftToDepthConfig.fEmitterDCmosDistance); + shiftToDepthConfig.nDeviceMaxShiftValue = XN_PREPARE_VAR32_IN_BUFFER(linkShiftToDepthConfig.nDeviceMaxShiftValue); + shiftToDepthConfig.nDeviceMaxDepthValue = XN_PREPARE_VAR32_IN_BUFFER(linkShiftToDepthConfig.nDeviceMaxDepthValue); + shiftToDepthConfig.nConstShift = XN_PREPARE_VAR32_IN_BUFFER(linkShiftToDepthConfig.nConstShift); + shiftToDepthConfig.nPixelSizeFactor = XN_PREPARE_VAR32_IN_BUFFER(linkShiftToDepthConfig.nPixelSizeFactor); + shiftToDepthConfig.nParamCoeff = XN_PREPARE_VAR32_IN_BUFFER(linkShiftToDepthConfig.nParamCoeff); + shiftToDepthConfig.nShiftScale = XN_PREPARE_VAR32_IN_BUFFER(linkShiftToDepthConfig.nShiftScale); + shiftToDepthConfig.nDepthMinCutOff = XN_PREPARE_VAR16_IN_BUFFER(linkShiftToDepthConfig.nDepthMinCutOff); + shiftToDepthConfig.nDepthMaxCutOff = XN_PREPARE_VAR16_IN_BUFFER(linkShiftToDepthConfig.nDepthMaxCutOff); + shiftToDepthConfig.dDepthScale = 1.0; // not controlled by FW +} + +void xnLinkParseCropping(OniCropping& cropping, const XnLinkCropping& linkCropping) +{ + cropping.enabled = linkCropping.m_bEnabled; + cropping.originX = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nXOffset); + cropping.originY = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nYOffset); + cropping.width = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nXSize); + cropping.height = XN_PREPARE_VAR16_IN_BUFFER(linkCropping.m_nYSize); +} + +void xnLinkEncodeCropping(XnLinkCropping& linkCropping, const OniCropping& cropping) +{ + linkCropping.m_bEnabled = XnUInt8(cropping.enabled); + linkCropping.m_nReserved1 = 0; + linkCropping.m_nReserved2 = 0; + linkCropping.m_nReserved3 = 0; + linkCropping.m_nXOffset = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.originX); + linkCropping.m_nYOffset = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.originY); + linkCropping.m_nXSize = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.width); + linkCropping.m_nYSize = XN_PREPARE_VAR16_IN_BUFFER((XnUInt16)cropping.height); +} + +const XnChar* xnLinkGetPropName(XnLinkPropID propID) +{ + /* To build this switch, paste the contents of the XnLinkPropID enum here, and then use + visual studio's search & replace with regex, and replace this: + {XN_LINK_PROP_ID_{:i}}:b*=.* + Into this: + case \1:\n\t\t\t\treturn "\2"; + + Or just code it by hand if it relaxes you. + */ + + switch (propID) + { + case XN_LINK_PROP_ID_NONE: + return "NONE"; + case XN_LINK_PROP_ID_CONTROL_MAX_PACKET_SIZE: + return "CONTROL_MAX_PACKET_SIZE"; + case XN_LINK_PROP_ID_FW_VERSION: + return "FW_VERSION"; + case XN_LINK_PROP_ID_PROTOCOL_VERSION: + return "PROTOCOL_VERSION"; + case XN_LINK_PROP_ID_SUPPORTED_MSG_TYPES: + return "SUPPORTED_MSG_TYPES"; + case XN_LINK_PROP_ID_SUPPORTED_PROPS: + return "SUPPORTED_PROPS"; + case XN_LINK_PROP_ID_HW_VERSION: + return "HW_VERSION"; + case XN_LINK_PROP_ID_SERIAL_NUMBER: + return "SERIAL_NUMBER"; + case XN_LINK_PROP_ID_SUPPORTED_BIST_TESTS: + return "SUPPORTED_BIST_TESTS"; + case XN_LINK_PROP_ID_SUPPORTED_VIDEO_MODES: + return "SUPPORTED_VIDEO_MODES"; + case XN_LINK_PROP_ID_VIDEO_MODE: + return "VIDEO_MODE"; + case XN_LINK_PROP_ID_STREAM_SUPPORTED_INTERFACES: + return "STREAM_SUPPORTED_INTERFACES"; + case XN_LINK_PROP_ID_STREAM_FRAG_LEVEL: + return "STREAM_FRAG_LEVEL"; + case XN_LINK_PROP_ID_HAND_SMOOTHING: + return "HAND_SMOOTHING"; + case XN_LINK_PROP_ID_SUPPORTED_SKELETON_JOINTS: + return "SUPPORTED_SKELETON_JOINTS"; + case XN_LINK_PROP_ID_SUPPORTED_SKELETON_PROFILES: + return "SUPPORTED_SKELETON_PROFILES"; + case XN_LINK_PROP_ID_NEEDED_CALIBRATION_POSE: + return "NEEDED_CALIBRATION_POSE"; + case XN_LINK_PROP_ID_ACTIVE_JOINTS: + return "ACTIVE_JOINTS"; + case XN_LINK_PROP_ID_SKELETON_SMOOTHING: + return "SKELETON_SMOOTHING"; + case XN_LINK_PROP_ID_SUPPORTED_POSES: + return "SUPPORTED_POSES"; + case XN_LINK_PROP_ID_MIRROR: + return "MIRROR"; + case XN_LINK_PROP_ID_CROPPING: + return "CROPPING"; + case XN_LINK_PROP_ID_INVALID: + return "INVALID"; + default: + return "UNKNOWN"; + } +} + + + +XnStatus xnLinkValidateGeneralProp(XnLinkPropType propType, XnUInt32 nValueSize, XnUInt32 nMinSize) +{ + if (propType != XN_LINK_PROP_TYPE_GENERAL) + { + xnLogError(XN_MASK_LINK, "Property type should be %s, but got type %s", xnLinkPropTypeToStr(XN_LINK_PROP_TYPE_GENERAL), xnLinkPropTypeToStr(propType)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_TYPE; + } + + if (nValueSize < nMinSize) + { + xnLogError(XN_MASK_LINK, "Property value size should be at least %u bytes, but got %u bytes.", + nMinSize, nValueSize); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + return XN_STATUS_OK; +} + +XnStatus xnLinkParseLeanVersionProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, XnLeanVersion& leanVersion) +{ + XnStatus nRetVal = xnLinkValidateGeneralProp(propType, nValueSize, sizeof(XnLinkLeanVersion)); + XN_IS_STATUS_OK_LOG_ERROR("Validate version property", nRetVal); + xnLinkParseLeanVersion(leanVersion, *reinterpret_cast(pValue)); + return XN_STATUS_OK; +} + +XnStatus xnLinkParseIDSetProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::Array& idSet) +{ + XnStatus nRetVal = xnLinkValidateGeneralProp(propType, nValueSize, sizeof(XnLinkIDSetHeader)); + XN_IS_STATUS_OK_LOG_ERROR("Validate id set property", nRetVal); + nRetVal = xnLinkParseIDSet(idSet, pValue, nValueSize); + XN_IS_STATUS_OK_LOG_ERROR("Parse id set", nRetVal); + return XN_STATUS_OK; +} + +XnStatus xnLinkParseBitSetProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::BitSet& bitSet) +{ + XnStatus nRetVal = xnLinkValidateGeneralProp(propType, nValueSize, sizeof(XnUInt32)); //min size of of m_nSize + XN_IS_STATUS_OK_LOG_ERROR("Validate id set property", nRetVal); + nRetVal = xnLinkParseBitSet(bitSet, reinterpret_cast(pValue), nValueSize); + XN_IS_STATUS_OK_LOG_ERROR("Parse bit set", nRetVal); + return XN_STATUS_OK; +} + +XnStatus xnLinkParseFrameSyncStreamIDsProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::Array& streamIDs) +{ + XnStatus nRetVal = xnLinkValidateGeneralProp(propType, nValueSize, sizeof(XnUInt16)); //Min size is of m_nNumStreamIDs + XN_IS_STATUS_OK_LOG_ERROR("Validate frame sync stream IDs property", nRetVal); + nRetVal = xnLinkParseFrameSyncStreamIDs(streamIDs, pValue, nValueSize); + XN_IS_STATUS_OK_LOG_ERROR("Parse frame sync stream IDs", nRetVal); + return XN_STATUS_OK; +} + +XnStatus xnLinkParseComponentVersionsListProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::Array& componentVersions) +{ + const XnLinkComponentVersionsList* pLinkList = reinterpret_cast(pValue); + XnStatus nRetVal = xnLinkValidateGeneralProp(propType, nValueSize, sizeof(pLinkList->m_nCount)); //Min size is the count field + XN_IS_STATUS_OK_LOG_ERROR("Validate components versions list property", nRetVal); + nRetVal = xnLinkParseComponentVersionsList(componentVersions, pLinkList, nValueSize); + XN_IS_STATUS_OK_LOG_ERROR("Parse frame sync stream IDs", nRetVal); + return XN_STATUS_OK; +} + +XnUInt32 xnLinkGetPixelSizeByStreamType(XnLinkStreamType streamType) +{ + switch (streamType) + { + case XN_LINK_STREAM_TYPE_SHIFTS: + return sizeof(OniDepthPixel); + case XN_LINK_STREAM_TYPE_IR: + return sizeof(OniGrayscale16Pixel); + case XN_LINK_STREAM_TYPE_COLOR: + return sizeof(OniYUV422DoublePixel)/2; //TODO: different pixel formats + case XN_LINK_STREAM_TYPE_DY: + return sizeof(XnUInt16); + default: + xnLogError(XN_MASK_LINK, "Bad stream type: %u", streamType); + XN_ASSERT(FALSE); + return 0; + } +} + +XnStatus xnLinkParseSupportedI2CDevices(const XnLinkSupportedI2CDevices* pDevicesList, XnUInt32 nBufferSize, xnl::Array& supportedDevices) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nDevices = 0; + XnUInt32 nExpectedSize = 0; + + XN_VALIDATE_INPUT_PTR(pDevicesList); + + if (nBufferSize < sizeof(pDevicesList->m_nCount)) + { + xnLogError(XN_MASK_LINK, "Size of link supported devices list was only %u bytes, must be at least %u.", nBufferSize, + sizeof(pDevicesList->m_nCount)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + nDevices = XN_PREPARE_VAR32_IN_BUFFER(pDevicesList->m_nCount); + nExpectedSize = (sizeof(pDevicesList->m_nCount) + + (sizeof(pDevicesList->m_aI2CDevices[0]) * nDevices)); + if (nBufferSize != nExpectedSize) + { + xnLogError(XN_MASK_LINK, "Got bad size of 'supported devices list' property: %u instead of %u", nBufferSize, nExpectedSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = supportedDevices.SetSize(nDevices); + XN_IS_STATUS_OK_LOG_ERROR("Set size of output supported device array", nRetVal); + + for (XnUInt32 i = 0; i < nDevices; i++) + { + supportedDevices[i].m_nID = XN_PREPARE_VAR32_IN_BUFFER(pDevicesList->m_aI2CDevices[i].m_nID); + nRetVal = xnOSStrCopy(supportedDevices[i].m_strName, pDevicesList->m_aI2CDevices[i].m_strName, sizeof(supportedDevices[i].m_strName)); + XN_IS_STATUS_OK_LOG_ERROR("Copy I2C device name", nRetVal); + } + + return XN_STATUS_OK; +} + +XnStatus xnLinkParseSupportedLogFiles(const XnLinkSupportedLogFiles* pFilesList, XnUInt32 nBufferSize, xnl::Array& supportedFiles) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nLogFiles = 0; + XnUInt32 nExpectedSize = 0; + + XN_VALIDATE_INPUT_PTR(pFilesList); + + if (nBufferSize < sizeof(pFilesList->m_nCount)) + { + xnLogError(XN_MASK_LINK, "Size of link supported files list was only %u bytes, must be at least %u.", nBufferSize, + sizeof(pFilesList->m_nCount)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + nLogFiles = XN_PREPARE_VAR32_IN_BUFFER(pFilesList->m_nCount); + nExpectedSize = (sizeof(pFilesList->m_nCount) + + (sizeof(pFilesList->m_aLogFiles[0]) * nLogFiles)); + if (nBufferSize != nExpectedSize) + { + xnLogError(XN_MASK_LINK, "Got bad size of 'supported log files list' property: %u instead of %u", nBufferSize, nExpectedSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = supportedFiles.SetSize(nLogFiles); + XN_IS_STATUS_OK_LOG_ERROR("Set size of output supported log files array", nRetVal); + + for (XnUInt32 i = 0; i < nLogFiles; i++) + { + supportedFiles[i].m_nID = XN_PREPARE_VAR32_IN_BUFFER(pFilesList->m_aLogFiles[i].m_nID); + nRetVal = xnOSStrCopy(supportedFiles[i].m_strName, pFilesList->m_aLogFiles[i].m_strName, sizeof(supportedFiles[i].m_strName)); + XN_IS_STATUS_OK_LOG_ERROR("Copy log file name", nRetVal); + } + + return XN_STATUS_OK; +} + +XnStatus xnLinkParseSupportedBistTests(const XnLinkSupportedBistTests* pSupportedTests, XnUInt32 nBufferSize, xnl::Array& supportedTests) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nTests = 0; + XnUInt32 nExpectedSize = 0; + + XN_VALIDATE_INPUT_PTR(pSupportedTests); + + if (nBufferSize < sizeof(pSupportedTests->m_nCount)) + { + xnLogError(XN_MASK_LINK, "Size of link supported BIST tests was only %u bytes, must be at least %u.", nBufferSize, + sizeof(pSupportedTests->m_nCount)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + nTests = XN_PREPARE_VAR32_IN_BUFFER(pSupportedTests->m_nCount); + nExpectedSize = (sizeof(pSupportedTests->m_nCount) + + (sizeof(pSupportedTests->m_aTests[0]) * nTests)); + if (nBufferSize != nExpectedSize) + { + xnLogError(XN_MASK_LINK, "Got bad size of 'supported BIST tests' property: %u instead of %u", nBufferSize, nExpectedSize); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_RESPONSE_SIZE; + } + + nRetVal = supportedTests.SetSize(nTests); + XN_IS_STATUS_OK_LOG_ERROR("Set size of output supported BIST tests array", nRetVal); + + for (XnUInt32 i = 0; i < nTests; i++) + { + supportedTests[i].id = XN_PREPARE_VAR32_IN_BUFFER(pSupportedTests->m_aTests[i].m_nID); + nRetVal = xnOSStrCopy(supportedTests[i].name, pSupportedTests->m_aTests[i].m_strName, sizeof(supportedTests[i].name)); + XN_IS_STATUS_OK_LOG_ERROR("Copy BIST test name", nRetVal); + } + + return XN_STATUS_OK; +} + +const XnChar* xnLinkPixelFormatToName(XnFwPixelFormat pixelFormat) +{ + switch (pixelFormat) + { + case XN_FW_PIXEL_FORMAT_SHIFTS_9_3: + return "Shifts9.3"; + case XN_FW_PIXEL_FORMAT_GRAYSCALE16: + return "Grayscale16"; + case XN_FW_PIXEL_FORMAT_YUV422: + return "YUV422"; + case XN_FW_PIXEL_FORMAT_BAYER8: + return "BAYER8"; + default: + XN_ASSERT(FALSE); + return "UNKNOWN"; + } +} + +XnFwPixelFormat xnLinkPixelFormatFromName(const XnChar* name) +{ + if (xnOSStrCmp(name, "Shifts9.3") == 0) + return XN_FW_PIXEL_FORMAT_SHIFTS_9_3; + else if (xnOSStrCmp(name, "Grayscale16") == 0) + return XN_FW_PIXEL_FORMAT_GRAYSCALE16; + else if (xnOSStrCmp(name, "YUV422") == 0) + return XN_FW_PIXEL_FORMAT_YUV422; + else if (xnOSStrCmp(name, "BAYER8") == 0) + return XN_FW_PIXEL_FORMAT_BAYER8; + else + { + XN_ASSERT(FALSE); + return (XnFwPixelFormat)(-1); + } +} + +const XnChar* xnLinkCompressionToName(XnFwCompressionType compression) +{ + switch (compression) + { + case XN_FW_COMPRESSION_NONE: + return "None"; + case XN_FW_COMPRESSION_8Z: + return "8z"; + case XN_FW_COMPRESSION_16Z: + return "16z"; + case XN_FW_COMPRESSION_24Z: + return "24z"; + case XN_FW_COMPRESSION_6_BIT_PACKED: + return "6bit"; + case XN_FW_COMPRESSION_10_BIT_PACKED: + return "10bit"; + case XN_FW_COMPRESSION_11_BIT_PACKED: + return "11bit"; + case XN_FW_COMPRESSION_12_BIT_PACKED: + return "12bit"; + default: + XN_ASSERT(FALSE); + return "UNKNOWN"; + } +} + +XnFwCompressionType xnLinkCompressionFromName(const XnChar* name) +{ + if (xnOSStrCmp(name, "None") == 0) + return XN_FW_COMPRESSION_NONE; + else if (xnOSStrCmp(name, "8z") == 0) + return XN_FW_COMPRESSION_8Z; + else if (xnOSStrCmp(name, "16z") == 0) + return XN_FW_COMPRESSION_16Z; + else if (xnOSStrCmp(name, "24z") == 0) + return XN_FW_COMPRESSION_24Z; + else if (xnOSStrCmp(name, "6bit") == 0) + return XN_FW_COMPRESSION_6_BIT_PACKED; + else if (xnOSStrCmp(name, "10bit") == 0) + return XN_FW_COMPRESSION_10_BIT_PACKED; + else if (xnOSStrCmp(name, "11bit") == 0) + return XN_FW_COMPRESSION_11_BIT_PACKED; + else if (xnOSStrCmp(name, "12bit") == 0) + return XN_FW_COMPRESSION_12_BIT_PACKED; + else + { + XN_ASSERT(FALSE); + return (XnFwCompressionType)-1; + } +} + +void xnLinkVideoModeToString(XnFwStreamVideoMode videoMode, XnChar* buffer, XnUInt32 bufferSize) +{ + XnUInt32 charsWritten = 0; + xnOSStrFormat(buffer, bufferSize, &charsWritten, "%ux%u@%u (%s, %s)", + videoMode.m_nXRes, videoMode.m_nYRes, videoMode.m_nFPS, + xnLinkPixelFormatToName(videoMode.m_nPixelFormat), + xnLinkCompressionToName(videoMode.m_nCompression)); +} + +void xnLinkParseBootStatus(XnBootStatus& bootStatus, const XnLinkBootStatus& linkBootStatus) +{ + bootStatus.errorCode = (XnBootErrorCode)XN_PREPARE_VAR32_IN_BUFFER(linkBootStatus.m_nErrorCode); + bootStatus.zone = (XnFileZone)XN_PREPARE_VAR32_IN_BUFFER(linkBootStatus.m_nZone); +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.h new file mode 100644 index 0000000..d3f16b7 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkProtoUtils.h @@ -0,0 +1,163 @@ +#ifndef __XNLINKPROTOUTILS_H__ +#define __XNLINKPROTOUTILS_H__ + +#include "XnLinkDefs.h" +#include "XnLinkProto.h" +#include "XnLinkStatusCodes.h" +#include "XnShiftToDepth.h" +#include +#include +#include +#include +#include +#include + +#define XN_MASK_LINK "xnLink" + +#ifndef XN_COMPILER_ASSERT +#define XN_COMPILER_ASSERT(x) typedef int compileAssert[x ? 1 : -1] +#endif + +template +class XnArray; + +namespace xn +{ + +class LinkPacketHeader : private XnLinkPacketHeader +{ +public: + XnStatus Validate(XnUInt32 nBytesToRead) const; + XnBool IsMagicValid() const { return (m_nMagic == XN_LINK_MAGIC); } + XnUInt16 GetSize() const { return m_nSize; } + XnUInt16 GetDataSize() const { return (m_nSize - sizeof(XnLinkPacketHeader)); } + XnUInt16 GetMsgType() const { return m_nMsgType; } + XnLinkFragmentation GetFragmentationFlags() const { return XnLinkFragmentation(m_nFragmentation); } + XnUInt16 GetStreamID() const { return m_nStreamID; } + XnUInt16 GetPacketID() const { return m_nPacketID; } + XnUInt16 GetCID() const { return m_nCID; } + const XnUInt8* GetPacketData() const {return (reinterpret_cast(this) + sizeof(XnLinkPacketHeader)); } + XnUInt8* GetPacketData() {return (reinterpret_cast(this) + sizeof(XnLinkPacketHeader)); } + + void SetMagic() { m_nMagic = XN_LINK_MAGIC; } + void SetSize(XnUInt16 nSize) { m_nSize = nSize; } + void SetMsgType(XnUInt16 nMsgType) { m_nMsgType = nMsgType; } + void SetFragmentationFlags(XnLinkFragmentation flags) { m_nFragmentation = flags; } + void SetStreamID(XnUInt16 nStreamID) { m_nStreamID = nStreamID; } + void SetPacketID(XnUInt16 nPacketID) { m_nPacketID = nPacketID; } + void SetCID(XnUInt16 nCID) { m_nCID = nCID; } +}; + +XN_COMPILER_ASSERT(sizeof(xn::LinkPacketHeader) == sizeof(XnLinkPacketHeader)); + +} + +XnStatus xnLinkResponseCodeToStatus(XnUInt16 nResponseCode); +const XnChar* xnLinkResponseCodeToStr(XnUInt16 nResponseCode); +const XnChar* xnFragmentationFlagsToStr(XnLinkFragmentation fragmentation); + +const XnChar* xnLinkStreamTypeToString(XnStreamType streamType); +XnStreamType xnLinkStreamTypeFromString(const XnChar* strType); + +const XnChar* xnLinkGestureTypeToName(XnUInt32 gestureType); +XnUInt32 xnLinkGestureNameToType(const XnChar* strGesture); + +const XnChar* xnLinkPixelFormatToName(XnFwPixelFormat pixelFormat); +XnFwPixelFormat xnLinkPixelFormatFromName(const XnChar* name); +const XnChar* xnLinkCompressionToName(XnFwCompressionType compression); +XnFwCompressionType xnLinkCompressionFromName(const XnChar* name); + +const XnChar* xnLinkPoseTypeToName(XnUInt32 poseType); +XnUInt32 xnLinkPoseNameToType(const XnChar* strPose); +XnStatus xnLinkPosesToNames(XnUInt32 nPoses, xnl::Array& aPosesNames); + +xnl::Point3D xnLinkPoint3DToPoint3D(const XnLinkPoint3D& point); +XnLinkPoint3D XnPoint3DToLinkPoint3D(const xnl::Point3D& point); + +XnLinkBoundingBox3D xnBoundingBox3DToLinkBoundingBox3D(const xnl::Box3D& box); +xnl::Box3D xnLinkBoundingBox3DToBoundingBox3D(const XnLinkBoundingBox3D& box); + +XnStatus xnLinkGetStreamDumpName(XnUInt16 nStreamID, XnChar* strDumpName, XnUInt32 nDumpNameSize); +XnStatus xnLinkGetEPDumpName(XnUInt16 nEPID, XnChar* strDumpName, XnUInt32 nDumpNameSize); + +XnStatus xnLinkParseIDSet(xnl::Array& idSet, const void* pIDSet, XnUInt32 nSize); + +/*pnEncodedSize is max size on input, actual size on output. pIDs is an array of uint16 values that must be grouped by interface ID.*/ +XnStatus xnLinkEncodeIDSet(void* pIDSet, XnUInt32 *pnEncodedSize, const XnUInt16* pIDs, XnUInt32 nNumIDs); + +XnStatus xnLinkParseFrameSyncStreamIDs(xnl::Array& frameSyncStreamIDs, const void* pFrameSyncStreamIDs, XnUInt32 nBufferSize); +//nBufferSize is max size on input, actual size on output +XnStatus xnLinkEncodeFrameSyncStreamIDs(void* pFrameSyncStreamIDs, XnUInt32& nBufferSize, const xnl::Array& frameSyncStreamIDs); +XnStatus xnLinkParseComponentVersionsList(xnl::Array& componentVersions, const XnLinkComponentVersionsList* pLinkList, XnUInt32 nBufferSize); + +/* +XnUInt8 xnLinkNICapabilityToInterfaceID(const XnChar* strCapabilityName); +const XnChar* xnLinkInterfaceIDToNICapability(XnUInt8 nInterfaceID); +XnProductionNodeType xnLinkStreamTypeToNINodeType(XnLinkStreamType streamType); +XnLinkStreamType xnLinkNINodeTypeToStreamType(XnProductionNodeType nodeType); +*/ +void xnLinkParseVideoMode(XnFwStreamVideoMode& videoMode, const XnLinkVideoMode& linkVideoMode); +void xnLinkEncodeVideoMode(XnLinkVideoMode& linkVideoMode, const XnFwStreamVideoMode& videoMode); + +const XnChar* xnLinkPropTypeToStr(XnLinkPropType propType); + +void xnLinkParseDetailedVersion(XnLinkDetailedVersion& version, const XnLinkDetailedVersion& linkVersion); + +void xnLinkParseLeanVersion(XnLeanVersion& version, const XnLinkLeanVersion& linkVersion); +void xnEncodeLeanVersion(XnLinkLeanVersion& linkVersion, const XnLeanVersion& version); + +/* nNumModes is max number of modes on input, actual number on output. */ +XnStatus xnLinkParseSupportedVideoModes(xnl::Array& aModes, + const XnLinkSupportedVideoModes* pLinkSupportedModes, + XnUInt32 nBufferSize); +XnStatus xnLinkParseBitSet(xnl::BitSet& bitSet, const XnLinkBitSet* pBitSet, XnUInt32 nBufferSize); +XnStatus xnLinkEncodeBitSet(XnLinkBitSet& linkBitSet, XnUInt32& nBufferSize, const xnl::BitSet& bitSet); + +void xnLinkParseShiftToDepthConfig(XnShiftToDepthConfig& shiftToDepthConfig, const XnLinkShiftToDepthConfig& linkShiftToDepthConfig); + +void xnLinkParseCropping(OniCropping& cropping, const XnLinkCropping& linkCropping); +void xnLinkEncodeCropping(XnLinkCropping& linkCropping, const OniCropping& cropping); + +const XnChar* xnLinkGetPropName(XnLinkPropID propID); + +XnStatus xnLinkValidateGeneralProp(XnLinkPropType propType, XnUInt32 nValueSize, XnUInt32 nMinSize); + +template +XnStatus xnLinkParseIntProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, T& nParsedVal) +{ + if (nValueSize < sizeof(T)) + { + xnLogError(XN_MASK_LINK, "Property value size should be at least %u bytes, but got only %u bytes.", + sizeof(T), nValueSize); + return XN_STATUS_LINK_BAD_PROP_SIZE; + } + + if (propType != XN_LINK_PROP_TYPE_INT) + { + xnLogError(XN_MASK_LINK, "Property type should be %s, but got type %s", + xnLinkPropTypeToStr(XN_LINK_PROP_TYPE_INT), + xnLinkPropTypeToStr(propType)); + XN_ASSERT(FALSE); + return XN_STATUS_LINK_BAD_PROP_TYPE; + } + + nParsedVal = static_cast(XN_PREPARE_VAR64_IN_BUFFER(*reinterpret_cast(pValue))); + return XN_STATUS_OK; +} + +XnStatus xnLinkParseLeanVersionProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, XnLeanVersion& leanVersion); +XnStatus xnLinkParseIDSetProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::Array& idSet); +XnStatus xnLinkParseBitSetProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::BitSet& bitSet); +XnStatus xnLinkParseFrameSyncStreamIDsProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::Array& streamIDs); +XnStatus xnLinkParseComponentVersionsListProp(XnLinkPropType propType, const void* pValue, XnUInt32 nValueSize, xnl::Array& componentVersions); + +XnStatus xnLinkParseSupportedBistTests(const XnLinkSupportedBistTests* pSupportedTests, XnUInt32 nBufferSize, xnl::Array& supportedTests); +XnStatus xnLinkParseSupportedI2CDevices(const XnLinkSupportedI2CDevices* pSupportedTests, XnUInt32 nBufferSize, xnl::Array& supportedDevices); +XnStatus xnLinkParseSupportedLogFiles(const XnLinkSupportedLogFiles* pFilesList, XnUInt32 nBufferSize, xnl::Array& supportedFiles); + +void xnLinkParseBootStatus(XnBootStatus& bootStatus, const XnLinkBootStatus& linkBootStatus); + +XnUInt32 xnLinkGetPixelSizeByStreamType(XnLinkStreamType streamType); + +void xnLinkVideoModeToString(XnFwStreamVideoMode videoMode, XnChar* buffer, XnUInt32 bufferSize); +#endif // __XNLINKPROTOUTILS_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.cpp new file mode 100644 index 0000000..740fe2f --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.cpp @@ -0,0 +1,59 @@ +#include "XnLinkResponseMsgParser.h" +#include "XnLinkStatusCodes.h" +#include "XnLinkProtoUtils.h" +#include +#include + +#define XN_MASK_LINK "xnLink" + +namespace xn +{ + +XnStatus LinkResponseMsgParser::ParsePacketImpl(XnLinkFragmentation /*fragmentation*/, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + if (nPacketDataSize < sizeof(XnLinkResponseInfo)) + { + XN_ASSERT(FALSE); + return XN_STATUS_LINK_MISSING_RESPONSE_INFO; + } + XnUInt16 nResponseCode = XN_PREPARE_VAR16_IN_BUFFER(((XnLinkResponseInfo*)pSrc)->m_nResponseCode); + + nPacketDataSize -= sizeof(XnLinkResponseInfo); + pSrc += sizeof(XnLinkResponseInfo); + + if (pDst + nPacketDataSize > pDstEnd) + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + //////////////////////////////////////////// + xnOSMemCopy(pDst, pSrc, nPacketDataSize); + //////////////////////////////////////////// + nRetVal = xnLinkResponseCodeToStatus(nResponseCode); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "Received error from link layer response: '%s' (%u)", + xnGetStatusString(nRetVal), nResponseCode); + xnLogWriteBinaryData(XN_MASK_LINK, XN_LOG_WARNING, + __FILE__, __LINE__, (XnUChar*)pSrc, (XnUInt32)nPacketDataSize, "Response extra data: "); + + XN_ASSERT(FALSE); + return nRetVal; + } + + pDst += nPacketDataSize; + + return XN_STATUS_OK; +} + + + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.h new file mode 100644 index 0000000..5da6315 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkResponseMsgParser.h @@ -0,0 +1,22 @@ +#ifndef __XNLINKRESPONSEMSGPARSER_H__ +#define __XNLINKRESPONSEMSGPARSER_H__ + +#include "XnLinkMsgParser.h" + +namespace xn +{ + +class LinkResponseMsgParser : public LinkMsgParser +{ +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); +}; + +} + +#endif // __XNLINKRESPONSEMSGPARSER_H__ + diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.cpp new file mode 100644 index 0000000..35f4f2b --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.cpp @@ -0,0 +1,4 @@ + +// registration is done by including XnStatusRegister *before* including the list of errors +#include +#include "XnLinkStatusCodes.h" diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.h new file mode 100644 index 0000000..f460699 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkStatusCodes.h @@ -0,0 +1,118 @@ +#ifndef __XNLINKSTATUSCODES_H__ +#define __XNLINKSTATUSCODES_H__ + +#include +#include "XnLinkProtoLibDefs.h" + +#define XN_ERROR_GROUP_LINKPROTOLIB 6000 + +#define XN_PS_STATUS_MESSAGE_MAP_START(module) \ + XN_STATUS_MESSAGE_MAP_START_FROM(XN_ERROR_GROUP_PRIMESENSE, module) + +#define XN_PS_STATUS_MESSAGE_MAP_END(module) \ + XN_STATUS_MESSAGE_MAP_END_FROM(XN_ERROR_GROUP_PRIMESENSE, module) + +XN_PS_STATUS_MESSAGE_MAP_START(XN_ERROR_GROUP_LINKPROTOLIB) + +//31770 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_HEADER_SIZE, "Bad link layer header size") + +//31771 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_MAGIC, "Bad link layer magic") + +//31772 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_FRAGMENTATION, "Bad link layer fragmentation flags") + +//31773 +XN_STATUS_MESSAGE(XN_STATUS_LINK_PARTIAL_PACKET, "Received a partial link layer packet") + +//31774 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_STREAM_ID, "Bad stream ID in link layer packet") + +//31775 +XN_STATUS_MESSAGE(XN_STATUS_LINK_PACKETS_LOST, "One or more Link layer packets were lost") + +//31776 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESPONSE_MSG_TYPE_MISMATCH, "Response message type mismatch") + +//31777 +XN_STATUS_MESSAGE(XN_STATUS_LINK_MISSING_RESPONSE_INFO, "Response info missing in response packet") + +//31778 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_PENDING, "Link response: Response pending") + +//31779 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_BAD_FILE_TYPE, "Link response: Bad file type") + +//31780 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_CMD_ERROR, "Link response: General command error") + +//31781 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_CMD_NOT_SUPPORTED, "Link response: Command not supported") //FW replied that command is not supported + +//31782 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_BAD_CMD_SIZE, "Link response: Bad command size") + +//31783 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_BAD_PARAMETERS, "Link response: bad parameters") + +//31784 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_CORRUPT_PACKET, "Link response: Corrupt packet") + +//31785 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESERVED1, "RESERVED1") + +//31786 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESERVED2, "RESERVED2") + +//31787 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESERVED3, "RESERVED3") + +//31788 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESERVED4, "RESERVED4") + +//31789 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESP_UNKNOWN, "Link response: Unknown response code") + +//31790 +XN_STATUS_MESSAGE(XN_STATUS_LINK_INVALID_MAX_SHIFT, "Max shift value is too big") + +//31791 +XN_STATUS_MESSAGE(XN_STATUS_LINK_INVALID_MAX_DEPTH, "Max depth value is too big") + +//31792 +XN_STATUS_MESSAGE(XN_STATUS_LINK_MISSING_TIMESTAMP, "Missing timestamp in data unit") + +//31793 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_RESPONSE_SIZE, "Response message has incorrect size") + +//31794 +XN_STATUS_MESSAGE(XN_STATUS_LINK_RESERVED5, "RESERVED5") + +//31795 +XN_STATUS_MESSAGE(XN_STATUS_LINK_UNKNOWN_GESTURE, "Unknown gesture") + +//31796 +XN_STATUS_MESSAGE(XN_STATUS_LINK_UNKNOWN_POSE, "Unknown pose") + +//31796 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_PACKET_FORMAT, "Bad packet format") + +//31797 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_PROP_TYPE, "Bad property type") + +//31798 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_PROP_ID, "Bad property ID") + +//31799 +XN_STATUS_MESSAGE(XN_STATUS_LINK_CMD_NOT_SUPPORTED, "Command not supported") //Command not supported as indicated by supported msg types by FW + +//31800 +XN_STATUS_MESSAGE(XN_STATUS_LINK_PROP_NOT_SUPPORTED, "Property not supported") //Property not supported as indicated by supported properties by FW + +//31801 +XN_STATUS_MESSAGE(XN_STATUS_LINK_BAD_PROP_SIZE, "Bad property size") + +XN_PS_STATUS_MESSAGE_MAP_END(XN_ERROR_GROUP_LINKPROTOLIB) + +#endif // __XNEESTATUSCODES_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.cpp new file mode 100644 index 0000000..174f948 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.cpp @@ -0,0 +1,50 @@ +#include "XnLinkUnpackedDataReductionParser.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkDefs.h" +#include +#include + +namespace xn +{ + + +const XnUInt16 LinkUnpackedDataReductionParser::FACTOR = 200; + + +LinkUnpackedDataReductionParser::LinkUnpackedDataReductionParser() +{ + +} + +LinkUnpackedDataReductionParser::~LinkUnpackedDataReductionParser() +{ + +} + +XnStatus LinkUnpackedDataReductionParser::ParsePacketImpl(XnLinkFragmentation /*fragmentation*/, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + + if ((pDst + nPacketDataSize) > pDstEnd) + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + //////////////////////////////////////////// + while (pSrc < pSrcEnd) + { + *((XnUInt16*)pDst) = *((XnUInt16*)pSrc) * FACTOR; + pDst += sizeof(XnUInt16); + pSrc += sizeof(XnUInt16); + } + //////////////////////////////////////////// + + return XN_STATUS_OK; +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.h new file mode 100644 index 0000000..ff2abb4 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedDataReductionParser.h @@ -0,0 +1,32 @@ +#ifndef __XNLINKUNPACKEDDATAREDUCTIONPARSER_H__ +#define __XNLINKUNPACKEDDATAREDUCTIONPARSER_H__ + +#include +#include "XnLinkDefs.h" +#include "XnLinkMsgParser.h" + +enum XnLinkFragmentation; +namespace xn +{ + +class LinkUnpackedDataReductionParser : public LinkMsgParser +{ +public: + LinkUnpackedDataReductionParser (); + virtual ~LinkUnpackedDataReductionParser(); + +protected: + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); + + + static const XnUInt16 FACTOR; + +}; + +} + +#endif // __XNLINKUNPACKEDDATAREDUCTIONPARSER_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.cpp new file mode 100644 index 0000000..9766a22 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.cpp @@ -0,0 +1,47 @@ +#include "XnLinkUnpackedS2DParser.h" +#include "XnShiftToDepth.h" +#include "XnLinkProtoUtils.h" +#include + +namespace xn +{ + +LinkUnpackedS2DParser::LinkUnpackedS2DParser(const XnShiftToDepthTables& shiftToDepthTables) : + m_shiftToDepthTables(shiftToDepthTables) +{ +} + +LinkUnpackedS2DParser::~LinkUnpackedS2DParser() +{ +} + +XnStatus LinkUnpackedS2DParser::ParsePacketImpl(XnLinkFragmentation /*fragmentation*/, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd) +{ + XN_ASSERT(m_shiftToDepthTables.bIsInitialized); + XnStatus nRetVal = XN_STATUS_OK; + XnSizeT nPacketDataSize = pSrcEnd - pSrc; + + if (pDst + nPacketDataSize > pDstEnd) + { + XN_ASSERT(FALSE); + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + + //////////////////////////////////////////// + nRetVal = XnShiftToDepthConvert(&m_shiftToDepthTables, + reinterpret_cast(pSrc), + XnUInt32(nPacketDataSize / 2), + reinterpret_cast(pDst)); + XN_IS_STATUS_OK(nRetVal); + //////////////////////////////////////////// + + pDst += nPacketDataSize; + + return XN_STATUS_OK; +} + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.h new file mode 100644 index 0000000..bd82d4b --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkUnpackedS2DParser.h @@ -0,0 +1,28 @@ +#ifndef __XNLINKUNPACKEDS2DPARSER_H__ +#define __XNLINKUNPACKEDS2DPARSER_H__ + +#include "XnLinkMsgParser.h" +#include "XnShiftToDepth.h" + +namespace xn +{ + +class LinkUnpackedS2DParser : public LinkMsgParser +{ +public: + LinkUnpackedS2DParser(const XnShiftToDepthTables& shiftToDepthTables); + virtual ~LinkUnpackedS2DParser(); + + virtual XnStatus ParsePacketImpl(XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); +private: + LinkUnpackedS2DParser& operator=(const LinkUnpackedS2DParser&); + const XnShiftToDepthTables& m_shiftToDepthTables; +}; + +} + +#endif // __XNLINKUNPACKEDS2DPARSER_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.cpp new file mode 100644 index 0000000..1922f06 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.cpp @@ -0,0 +1,32 @@ +#include "XnLinkYuv422ToRgb888Parser.h" +#include "XnLinkYuvToRgb.h" + +namespace xn +{ + +LinkYuv422ToRgb888Parser::LinkYuv422ToRgb888Parser() +{ + +} + +LinkYuv422ToRgb888Parser::~LinkYuv422ToRgb888Parser() +{ + +} + +XnStatus LinkYuv422ToRgb888Parser::ParsePacketImpl(XnLinkFragmentation /*fragmentation*/, const XnUInt8* pSrc, const XnUInt8* pSrcEnd, XnUInt8*& pDst, const XnUInt8* pDstEnd) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnSizeT dstSize = pDstEnd - pDst; + nRetVal = LinkYuvToRgb::Yuv422ToRgb888(pSrc, pSrcEnd - pSrc, pDst, dstSize); + XN_IS_STATUS_OK(nRetVal); + + pDst += dstSize; + + return (XN_STATUS_OK); +} + +} + + diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.h new file mode 100644 index 0000000..3a6d997 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuv422ToRgb888Parser.h @@ -0,0 +1,26 @@ +#ifndef _XN_LINK_YUV_422_TO_RGB_888_PARSER_H_ +#define _XN_LINK_YUV_422_TO_RGB_888_PARSER_H_ + +#include "XnLinkMsgParser.h" + +namespace xn +{ + +class LinkYuv422ToRgb888Parser : public LinkMsgParser +{ +public: + LinkYuv422ToRgb888Parser(); + virtual ~LinkYuv422ToRgb888Parser(); + +protected: + virtual XnStatus ParsePacketImpl( + XnLinkFragmentation fragmentation, + const XnUInt8* pSrc, + const XnUInt8* pSrcEnd, + XnUInt8*& pDst, + const XnUInt8* pDstEnd); +}; + +} + +#endif //_XN_LINK_YUV_422_TO_RGB_888_PARSER_H_ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.cpp new file mode 100644 index 0000000..6c775ba --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.cpp @@ -0,0 +1,154 @@ +#include +#include +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + #include +#endif + +#include "XnLinkYuvToRgb.h" + +#define YUV422_U 0 +#define YUV422_Y1 1 +#define YUV422_V 2 +#define YUV422_Y2 3 + +#define RGB888_RED 0 +#define RGB888_GREEN 1 +#define RGB888_BLUE 2 + +namespace xn +{ + +/* +http://en.wikipedia.org/wiki/YUV + +From YUV to RGB: +R = Y + 1.13983 V +G = Y - 0.39466 U - 0.58060 V +B = Y + 2.03211 U +*/ +XnStatus LinkYuvToRgb::Yuv422ToRgb888(const XnUInt8* pSrc, XnSizeT srcSize, XnUInt8* pDst, XnSizeT& dstSize) +{ + if (dstSize < srcSize * RGB_888_BYTES_PER_PIXEL / YUV_422_BYTES_PER_PIXEL) + { + return XN_STATUS_OUTPUT_BUFFER_OVERFLOW; + } + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + const XnUInt8* pYUVLast = pSrc + srcSize - 8; + + const __m128 minus128 = _mm_set_ps1(-128); + const __m128 plus113983 = _mm_set_ps1(1.13983F); + const __m128 minus039466 = _mm_set_ps1(-0.39466F); + const __m128 minus058060 = _mm_set_ps1(-0.58060F); + const __m128 plus203211 = _mm_set_ps1(2.03211F); + const __m128 zero = _mm_set_ps1(0); + const __m128 plus255 = _mm_set_ps1(255); + + // define YUV floats + __m128 y; + __m128 u; + __m128 v; + + __m128 temp; + + // define RGB floats + __m128 r; + __m128 g; + __m128 b; + + // define RGB integers + __m128i iR; + __m128i iG; + __m128i iB; + + XnUInt32* piR = (XnUInt32*)&iR; + XnUInt32* piG = (XnUInt32*)&iG; + XnUInt32* piB = (XnUInt32*)&iB; + + while (pSrc <= pYUVLast) + { + // process 4 pixels at once (values should be ordered backwards) + y = _mm_set_ps(pSrc[YUV422_Y2 + YUV_422_BYTES_PER_PIXEL], pSrc[YUV422_Y1 + YUV_422_BYTES_PER_PIXEL], pSrc[YUV422_Y2], pSrc[YUV422_Y1]); + u = _mm_set_ps(pSrc[YUV422_U + YUV_422_BYTES_PER_PIXEL], pSrc[YUV422_U + YUV_422_BYTES_PER_PIXEL], pSrc[YUV422_U], pSrc[YUV422_U]); + v = _mm_set_ps(pSrc[YUV422_V + YUV_422_BYTES_PER_PIXEL], pSrc[YUV422_V + YUV_422_BYTES_PER_PIXEL], pSrc[YUV422_V], pSrc[YUV422_V]); + + u = _mm_add_ps(u, minus128); // u -= 128 + v = _mm_add_ps(v, minus128); // v -= 128 + + temp = _mm_mul_ps(plus113983, v); + r = _mm_add_ps(y, temp); + + temp = _mm_mul_ps(minus039466, u); + g = _mm_add_ps(y, temp); + temp = _mm_mul_ps(minus058060, v); + g = _mm_add_ps(g, temp); + + temp = _mm_mul_ps(plus203211, u); + b = _mm_add_ps(y, temp); + + // make sure no value is smaller than 0 + r = _mm_max_ps(r, zero); + g = _mm_max_ps(g, zero); + b = _mm_max_ps(b, zero); + + // make sure no value is bigger than 255 + r = _mm_min_ps(r, plus255); + g = _mm_min_ps(g, plus255); + b = _mm_min_ps(b, plus255); + + // convert floats to int16 (there is no conversion to uint8, just to int8). + iR = _mm_cvtps_epi32(r); + iG = _mm_cvtps_epi32(g); + iB = _mm_cvtps_epi32(b); + + // extract the 4 pixels RGB values. + // because we made sure values are between 0 and 255, we can just take the lower byte + // of each INT16 + pDst[0] = (XnUInt8)piR[0]; + pDst[1] = (XnUInt8)piG[0]; + pDst[2] = (XnUInt8)piB[0]; + + pDst[3] = (XnUInt8)piR[1]; + pDst[4] = (XnUInt8)piG[1]; + pDst[5] = (XnUInt8)piB[1]; + + pDst[6] = (XnUInt8)piR[2]; + pDst[7] = (XnUInt8)piG[2]; + pDst[8] = (XnUInt8)piB[2]; + + pDst[9] = (XnUInt8)piR[3]; + pDst[10] = (XnUInt8)piG[3]; + pDst[11] = (XnUInt8)piB[3]; + + // advance the streams + pSrc += 8; + pDst += 12; + } +#else + const XnUInt8* pCurrYUV = pSrc; + XnUInt8* pCurrRGB = pDst; + const XnUInt8* pLastYUV = pSrc + srcSize - YUV_422_BYTES_PER_PIXEL; + + while (pCurrYUV <= pLastYUV) + { + pCurrRGB[RGB888_RED] = XnUInt8(pCurrYUV[YUV422_Y1] + 1.13983 * pCurrYUV[YUV422_V] + 0.5); + pCurrRGB[RGB888_GREEN] = XnUInt8(pCurrYUV[YUV422_Y1] - 0.39466 * pCurrYUV[YUV422_U] - 0.58060 * pCurrYUV[YUV422_V] + 0.5); + pCurrRGB[RGB888_BLUE] = XnUInt8(pCurrYUV[YUV422_Y1] + 2.03211 * pCurrYUV[YUV422_U] + 0.5); + + pCurrRGB += RGB_888_BYTES_PER_PIXEL; + + pCurrRGB[RGB888_RED] = XnUInt8(pCurrYUV[YUV422_Y2] + 1.13983 * pCurrYUV[YUV422_V] + 0.5); + pCurrRGB[RGB888_GREEN] = XnUInt8(pCurrYUV[YUV422_Y2] - 0.39466 * pCurrYUV[YUV422_U] - 0.58060 * pCurrYUV[YUV422_V] + 0.5); + pCurrRGB[RGB888_BLUE] = XnUInt8(pCurrYUV[YUV422_Y2] + 2.03211 * pCurrYUV[YUV422_U] + 0.5); + + pCurrRGB += RGB_888_BYTES_PER_PIXEL; + pCurrYUV += YUV_422_BYTES_PER_PIXEL; + } +#endif + + dstSize = srcSize * RGB_888_BYTES_PER_PIXEL / YUV_422_BYTES_PER_PIXEL; + + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.h b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.h new file mode 100644 index 0000000..4cf7a3c --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnLinkYuvToRgb.h @@ -0,0 +1,20 @@ +#ifndef _XN_LINK_YUV_TO_RGB_H_ +#define _XN_LINK_YUV_TO_RGB_H_ + +namespace xn +{ + +class LinkYuvToRgb +{ +public: + enum { + YUV_422_BYTES_PER_PIXEL = 2, + RGB_888_BYTES_PER_PIXEL = 3 + }; + + static XnStatus Yuv422ToRgb888(const XnUInt8* pSrc, XnSizeT srcBytes, XnUInt8* pDst, XnSizeT& dstSize); +}; + +} + +#endif //_XN_LINK_YUV_TO_RGB_H_ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnMapSequenceListConverter.h b/Source/Drivers/PSLink/LinkProtoLib/XnMapSequenceListConverter.h new file mode 100644 index 0000000..aaa7f0a --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnMapSequenceListConverter.h @@ -0,0 +1,206 @@ +// XnMapSequenceListConverter.h + +#ifndef _XNMAPSEQUENCELISTCONVERTER_H_ +#define _XNMAPSEQUENCELISTCONVERTER_H_ + + +namespace xn +{ + +template +class XnMapSequenceListConverter +{ +private: + typedef XnUInt16 ValueType; + + struct ValueHeader + { + ValueType nValue; + XnUInt16 nSequences; + }; + + struct Sequence + { + XnUInt16 nOffset; + XnUInt16 nSequenceCount; + }; + + XnStatus FillMapTopDown(PixelType* pMap, XnUInt32 nHeight, XnUInt32 nWidth, XnUInt32& nX, XnUInt32& nY, XnUInt32 nPixels, PixelType val) + { + XnUInt32 nCurrIndex = nWidth * nY + nX; + + for (XnUInt32 pxl = 0; pxl < nPixels; pxl++ ) + { + // Check indexes + if (nX >= nWidth || nY >= nHeight) + { + return XN_STATUS_BAD_PARAM; + } + pMap[nCurrIndex] = val; + + nY++; + if (nY == nHeight) + { + // Move to next column start + nY = 0; + nX++; + nCurrIndex = nX; + } + else + { + // Move next row, same column + nCurrIndex += nWidth; + } + } + + return XN_STATUS_OK; + } + +public: + + // We processing the map up down first, because people are more likely to stand next to each other than on top of each other. + XnStatus MapToSequenceList(const PixelType* pMap, XnUInt32 nHeight, XnUInt32 nWidth, XnUChar* pSeqBuffer, XnUInt32& nSeqSize) + { + const PixelType NA_PIXEL = (PixelType)-1; + const PixelType MAX_PIXEL_SIZE = (1 << sizeof(ValueType) * 8) - 1; + + // Current sequence related data + XnUInt16 nOffsetCount = 0; + PixelType sequncePixel = NA_PIXEL; + + // Value related data + ValueHeader* pCurrValueHeader = NULL; + Sequence* pCurrSeq = NULL; + + // Output related data + XnUChar* pCurrOutput = pSeqBuffer; + XnUInt nCurrOutputSize = 0; + + const PixelType* pColumn = pMap; + for (XnUInt32 x = 0; x < nWidth; x++) + { + const PixelType* pCurr = pColumn; + + for (XnUInt32 y = 0; y < nHeight; y++) + { + // Make sure no pixel uses NA_PIXEL value and that the pixel will fit ValueHeader.nValue + if (*pCurr == NA_PIXEL || *pCurr > MAX_PIXEL_SIZE) + { + return XN_STATUS_BAD_TYPE; + } + + // See if the pixel is part of an offset + if (*pCurr == 0) + { + nOffsetCount++; + sequncePixel = NA_PIXEL; + pCurrSeq = NULL; + } + // See if the pixel is in a sequence + else if (*pCurr == sequncePixel) + { + XN_ASSERT(pCurrSeq); + pCurrSeq->nSequenceCount++; + } + // New sequnce start + else + { + // Check if we need to add ValueHeader + if (!pCurrValueHeader || pCurrValueHeader->nValue != *pCurr) + { + // New value in front of us, add value header + nCurrOutputSize += sizeof(ValueHeader); + if (nCurrOutputSize > nSeqSize) + return XN_STATUS_INVALID_BUFFER_SIZE; + + pCurrValueHeader = (ValueHeader*)pCurrOutput; + pCurrOutput += sizeof(ValueHeader); + + pCurrValueHeader->nValue = (ValueType)*pCurr; + pCurrValueHeader->nSequences = 1; + } + else + { + pCurrValueHeader->nSequences++; + } + + + // Store new sequence pixel + sequncePixel = *pCurr; + + // Add sequence data + nCurrOutputSize += sizeof(Sequence); + if (nCurrOutputSize > nSeqSize) + return XN_STATUS_INVALID_BUFFER_SIZE; + + pCurrSeq = (Sequence*)pCurrOutput; + pCurrSeq->nOffset = nOffsetCount; + pCurrSeq->nSequenceCount = 1; + nOffsetCount = 0; + + pCurrOutput += sizeof(Sequence); + } + + // Move to next row + pCurr += nWidth; + } + pColumn++; + } + + nSeqSize = nCurrOutputSize; + return XN_STATUS_OK; + } + + XnStatus SequenceListToMap(XnUChar* pSeqBuffer, XnUInt32 nSeqSize, PixelType* pMap, XnUInt32 nHeight, XnUInt32 nWidth) + { + XnUInt32 nCurrProcessed = 0; + XnUInt32 nX = 0; + XnUInt32 nY = 0; + + XnUChar* pCurr = pSeqBuffer; + + // Default all pixels + xnOSMemSet(pMap, 0, sizeof(PixelType) * nHeight * nWidth); + + while (nCurrProcessed != nSeqSize) + { + nCurrProcessed += sizeof(ValueHeader); + if (nCurrProcessed > nSeqSize) + return XN_STATUS_INVALID_BUFFER_SIZE; + + // Get the value header + ValueHeader* pCurrValue = (ValueHeader*)pCurr; + pCurr += sizeof(ValueHeader); + + // Loop on value sequences + for (XnUInt16 nSeq = 0; nSeq < pCurrValue->nSequences; nSeq++ ) + { + nCurrProcessed += sizeof(Sequence); + if (nCurrProcessed > nSeqSize) + return XN_STATUS_INVALID_BUFFER_SIZE; + Sequence* pSeq = (Sequence*)pCurr; + pCurr += sizeof(Sequence); + + // Add offset to x,y + XnUInt nNaiveY = nY + pSeq->nOffset; + nX += nNaiveY / nHeight; + if (nX >= nWidth) + return XN_STATUS_BAD_PARAM; + nY = nNaiveY % nHeight; + + // Fill value + XnStatus rc = FillMapTopDown(pMap, nHeight, nWidth, nX, nY, pSeq->nSequenceCount, (PixelType)pCurrValue->nValue); + XN_IS_STATUS_OK(rc); + + + } + } + + return XN_STATUS_OK; + } +}; + + +} + +#endif diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.cpp new file mode 100644 index 0000000..04ee85d --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.cpp @@ -0,0 +1,49 @@ +#include "XnServerSocketInConnection.h" +#include + +#define XN_MASK_SOCKETS "xnSockets" + +namespace xn +{ + +ServerSocketInConnection::ServerSocketInConnection() +{ + +} + +ServerSocketInConnection::~ServerSocketInConnection() +{ + Shutdown(); +} + +XnStatus ServerSocketInConnection::ConnectSocket(XN_SOCKET_HANDLE& hSocket, const XnChar* strIP, XnUInt16 nPort) +{ + XnStatus nRetVal = XN_STATUS_OK; + (void)strIP; //Ignore IP parameter - accept connections from any IP. + + XN_SOCKET_HANDLE hListenSocket = NULL; + nRetVal = xnOSCreateSocket(XN_OS_TCP_SOCKET, "0.0.0.0", nPort, &hListenSocket); + XN_IS_STATUS_OK_LOG_ERROR("Create data listen socket", nRetVal); + nRetVal = xnOSBindSocket(hListenSocket); + if (nRetVal != XN_STATUS_OK) + { + xnOSCloseSocket(hListenSocket); + XN_IS_STATUS_OK_LOG_ERROR("Bind data listen socket", nRetVal); + } + + nRetVal = xnOSListenSocket(hListenSocket); + if (nRetVal != XN_STATUS_OK) + { + xnOSCloseSocket(hListenSocket); + XN_IS_STATUS_OK_LOG_ERROR("Listen to data socket", nRetVal); + } + xnLogVerbose(XN_MASK_SOCKETS, "Server accepting %s:%u...", strIP, nPort); + nRetVal = xnOSAcceptSocket(hListenSocket, &hSocket, XN_WAIT_INFINITE); + xnOSCloseSocket(hListenSocket); + XN_IS_STATUS_OK_LOG_ERROR("Accept data socket", nRetVal); + xnLogVerbose(XN_MASK_SOCKETS, "Server accepted connection on port %u", nPort); + + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.h b/Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.h new file mode 100644 index 0000000..b620846 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnServerSocketInConnection.h @@ -0,0 +1,24 @@ + +#ifndef _XNSERVERSOCKETINCONNECTION_H_ +#define _XNSERVERSOCKETINCONNECTION_H_ + +#include "XnSocketInConnection.h" + + +namespace xn +{ + +class ServerSocketInConnection : public SocketInConnection +{ +public: + ServerSocketInConnection(); + virtual ~ServerSocketInConnection(); +protected: + virtual XnStatus ConnectSocket(XN_SOCKET_HANDLE& hSocket, const XnChar* strIP, XnUInt16 nPort); + +}; + +} + + +#endif \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxConnectionFactory.h b/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxConnectionFactory.h new file mode 100644 index 0000000..ca553e9 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxConnectionFactory.h @@ -0,0 +1,40 @@ +#ifndef __XNSERVERLINUXUSBCONNECTIONFACTORY_H__ +#define __XNSERVERLINUXUSBCONNECTIONFACTORY_H__ + +#include "IConnectionFactory.h" +#include "XnServerUSBLinuxControlEndpoint.h" +#include + +struct XnUSBDevice; + +namespace xn +{ + +class ServerUSBLinuxConnectionFactory : public IConnectionFactory +{ +public: + ServerUSBLinuxConnectionFactory(); + virtual ~ServerUSBLinuxConnectionFactory(); + + virtual XnStatus Init(const XnChar* strConnString); + virtual void Shutdown(); + virtual XnBool IsInitialized() const; + virtual XnUInt16 GetNumInputDataConnections() const; + virtual XnUInt16 GetNumOutputDataConnections() const; + + /** The pointer returned by GetControlConnection() belongs to the connection factory and + must not be deleted by caller. **/ + virtual XnStatus GetControlConnection(ISyncIOConnection*& pConn); + + virtual XnStatus CreateOutputDataConnection(XnUInt16 nID, IOutputConnection*& pConn); + virtual XnStatus CreateInputDataConnection(XnUInt16 nID, IAsyncInputConnection*& pConn); + +private: + ServerUSBLinuxControlEndpoint m_controlEndpoint; + XnUSBDevice* m_pDevice; + XnBool m_bInitialized; +}; + +} + +#endif // __XNSERVERLINUXUSBCONNECTIONFACTORY_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxControlEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxControlEndpoint.h new file mode 100644 index 0000000..53c1a4d --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxControlEndpoint.h @@ -0,0 +1,37 @@ +#include "ISyncIOConnection.h" +#include + +struct XnUSBDevice; + +namespace xn +{ + +class ServerUSBLinuxControlEndpoint : virtual public ISyncIOConnection +{ +public: + ServerUSBLinuxControlEndpoint(); + virtual ~ServerUSBLinuxControlEndpoint(); + + virtual XnStatus Init(XnUSBDevice* pUSBDevice, XnUInt16 nMaxPacketSize); + virtual void Shutdown(); + + virtual XnStatus Connect(); + virtual void Disconnect(); + virtual XnBool IsConnected() const; + virtual XnUInt16 GetMaxPacketSize() const; + + virtual XnStatus Receive(void* pData, XnUInt32& nSize); + virtual XnStatus Send(const void* pData, XnUInt32 nSize); + +private: + static const XnUInt32 RECEIVE_TIMEOUT; + + static void OnControlRequest(XnUSBDevice* pDevice, void* pCookie); + + XnUSBDevice* m_pUSBDevice; + XN_EVENT_HANDLE m_hControlEvent; + XnUInt16 m_nMaxPacketSize; + XnBool m_bConnected; +}; + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxOutDataEndpoint.h b/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxOutDataEndpoint.h new file mode 100644 index 0000000..025fff4 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnServerUSBLinuxOutDataEndpoint.h @@ -0,0 +1,33 @@ +#ifndef __XNSERVERUSBLINUXOUTDATAENDPOINT_H__ +#define __XNSERVERUSBLINUXOUTDATAENDPOINT_H__ + +#include "IOutputConnection.h" + +struct XnUSBDevice; + +namespace xn +{ + +class ServerUSBLinuxOutDataEndpoint : virtual public IOutputConnection +{ +public: + ServerUSBLinuxOutDataEndpoint(); + virtual ~ServerUSBLinuxOutDataEndpoint(); + virtual XnStatus Init(XnUSBDevice* pUSBDevice, XnUInt16 nEndpointID, XnUInt16 nMaxPacketSize); + virtual void Shutdown(); + virtual XnStatus Connect(); + virtual void Disconnect(); + virtual XnBool IsConnected() const; + virtual XnUInt16 GetMaxPacketSize() const; + virtual XnStatus Send(const void* pData, XnUInt32 nSize); + XnStatus Reset(); + +private: + XnUSBDevice* m_pUSBDevice; + XnUInt8 m_nEndpointID; + XnUInt16 m_nMaxPacketSize; +}; + +} + +#endif // __XNSERVERUSBLINUXOUTDATAENDPOINT_H__ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.cpp new file mode 100644 index 0000000..cc6cc4e --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.cpp @@ -0,0 +1,136 @@ +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include "XnShiftToDepth.h" +#include "XnLinkStatusCodes.h" + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- +XnStatus XnShiftToDepthInit(XnShiftToDepthTables* pShiftToDepth, const XnShiftToDepthConfig* pConfig) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + XN_VALIDATE_INPUT_PTR(pConfig); + + XN_VALIDATE_ALIGNED_CALLOC(pShiftToDepth->pShiftToDepthTable, OniDepthPixel, pConfig->nDeviceMaxShiftValue+1, XN_DEFAULT_MEM_ALIGN); + XN_VALIDATE_ALIGNED_CALLOC(pShiftToDepth->pDepthToShiftTable, XnUInt16, pConfig->nDeviceMaxDepthValue+1, XN_DEFAULT_MEM_ALIGN); + pShiftToDepth->bIsInitialized = TRUE; + + // store allocation sizes + pShiftToDepth->nShiftsCount = pConfig->nDeviceMaxShiftValue + 1; + pShiftToDepth->nDepthsCount = pConfig->nDeviceMaxDepthValue + 1; + + return XnShiftToDepthUpdate(pShiftToDepth, pConfig); +} + +XnStatus XnShiftToDepthUpdate(XnShiftToDepthTables* pShiftToDepth, const XnShiftToDepthConfig* pConfig) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + XN_VALIDATE_INPUT_PTR(pConfig); + + // check max shift wasn't changed (if so, memory should be re-allocated) + if (pConfig->nDeviceMaxShiftValue > pShiftToDepth->nShiftsCount) + return XN_STATUS_LINK_INVALID_MAX_SHIFT; + + // check max depth wasn't changed (if so, memory should be re-allocated) + if (pConfig->nDeviceMaxDepthValue > pShiftToDepth->nDepthsCount) + return XN_STATUS_LINK_INVALID_MAX_DEPTH; + + XnUInt16 nIndex = 0; + XnInt16 nShiftValue = 0; + XnDouble dFixedRefX = 0; + XnDouble dMetric = 0; + XnDouble dDepth = 0; + XnDouble dPlanePixelSize = pConfig->fZeroPlanePixelSize; + XnDouble dPlaneDsr = pConfig->nZeroPlaneDistance; + XnDouble dPlaneDcl = pConfig->fEmitterDCmosDistance; + XnInt32 nConstShift = pConfig->nParamCoeff * pConfig->nConstShift; + XnDouble dDepthScale = (pConfig->dDepthScale == 0) ? 1.0 : pConfig->dDepthScale; + + dPlanePixelSize *= pConfig->nPixelSizeFactor; + if (pConfig->nPixelSizeFactor == 0) + { + return XN_STATUS_ERROR; + } + nConstShift /= pConfig->nPixelSizeFactor; + + OniDepthPixel* pShiftToDepthTable = pShiftToDepth->pShiftToDepthTable; + XnUInt16* pDepthToShiftTable = pShiftToDepth->pDepthToShiftTable; + + xnOSMemSet(pShiftToDepthTable, 0, pShiftToDepth->nShiftsCount * sizeof(OniDepthPixel)); + xnOSMemSet(pDepthToShiftTable, 0, pShiftToDepth->nDepthsCount * sizeof(XnUInt16)); + + XnUInt16 nLastDepth = 0; + XnUInt16 nLastIndex = 0; + + for (nIndex = 1; nIndex < pConfig->nDeviceMaxShiftValue; nIndex++) + { + nShiftValue = nIndex; + + dFixedRefX = (XnDouble)(nShiftValue - nConstShift) / (XnDouble)pConfig->nParamCoeff; + dMetric = dFixedRefX * dPlanePixelSize; + dDepth = pConfig->nShiftScale * ((dMetric * dPlaneDsr / (dPlaneDcl - dMetric)) + dPlaneDsr) * dDepthScale; + + // check cut-offs + if ((dDepth > pConfig->nDepthMinCutOff) && (dDepth < pConfig->nDepthMaxCutOff)) + { + pShiftToDepthTable[nIndex] = (XnUInt16)dDepth; + + for (XnUInt16 i = nLastDepth; i < dDepth; i++) + pDepthToShiftTable[i] = nLastIndex; + + nLastIndex = nIndex; + nLastDepth = (XnUInt16)dDepth; + } + } + + for (XnUInt16 i = nLastDepth; i <= pConfig->nDeviceMaxDepthValue; i++) + pDepthToShiftTable[i] = nLastIndex; + + return XN_STATUS_OK; +} + +XnStatus XnShiftToDepthConvert(const XnShiftToDepthTables* pShiftToDepth, + const XnUInt16* pInput, + XnUInt32 nInputSize, + OniDepthPixel* pOutput) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + XN_VALIDATE_INPUT_PTR(pInput); + XN_VALIDATE_INPUT_PTR(pOutput); + + const XnUInt16* pInputEnd = pInput + nInputSize; + OniDepthPixel* pShiftToDepthTable = pShiftToDepth->pShiftToDepthTable; + + while (pInput != pInputEnd) + { + if (*pInput >= pShiftToDepth->nShiftsCount) + { + *pOutput = 0; + } + else + { + *pOutput = pShiftToDepthTable[*pInput]; + } + pOutput++; + pInput++; + } + + return XN_STATUS_OK; +} + +XnStatus XnShiftToDepthFree(XnShiftToDepthTables* pShiftToDepth) +{ + XN_VALIDATE_INPUT_PTR(pShiftToDepth); + + if (pShiftToDepth->bIsInitialized) + { + XN_ALIGNED_FREE_AND_NULL(pShiftToDepth->pDepthToShiftTable); + XN_ALIGNED_FREE_AND_NULL(pShiftToDepth->pShiftToDepthTable); + pShiftToDepth->bIsInitialized = FALSE; + } + + return XN_STATUS_OK; +} + diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.h b/Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.h new file mode 100644 index 0000000..48b2ac0 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnShiftToDepth.h @@ -0,0 +1,67 @@ + +#ifndef _XN_SHIFT_TO_DEPTH_H_ +#define _XN_SHIFT_TO_DEPTH_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef struct XnShiftToDepthConfig +{ + /** The zero plane distance in depth units. */ + OniDepthPixel nZeroPlaneDistance; + /** The zero plane pixel size */ + XnFloat fZeroPlanePixelSize; + /** The distance between the emitter and the Depth Cmos */ + XnFloat fEmitterDCmosDistance; + /** The maximum possible shift value from this device. */ + XnUInt32 nDeviceMaxShiftValue; + /** The maximum possible depth from this device (as opposed to a cut-off). */ + XnUInt32 nDeviceMaxDepthValue; + + XnUInt32 nConstShift; + XnUInt32 nPixelSizeFactor; + XnUInt32 nParamCoeff; + XnUInt32 nShiftScale; + + XnDouble dDepthScale; + OniDepthPixel nDepthMinCutOff; + OniDepthPixel nDepthMaxCutOff; + +} XnShiftToDepthConfig; + +typedef struct XnShiftToDepthTables +{ + XnBool bIsInitialized; + /** The shift-to-depth table. */ + OniDepthPixel* pShiftToDepthTable; + /** The number of entries in the shift-to-depth table. */ + XnUInt32 nShiftsCount; + /** The depth-to-shift table. */ + XnUInt16* pDepthToShiftTable; + /** The number of entries in the depth-to-shift table. */ + XnUInt32 nDepthsCount; +} XnShiftToDepthTables; + +//--------------------------------------------------------------------------- +// Functions Declaration +//--------------------------------------------------------------------------- +XnStatus XnShiftToDepthInit(XnShiftToDepthTables* pShiftToDepth, + const XnShiftToDepthConfig* pConfig); + +XnStatus XnShiftToDepthUpdate(XnShiftToDepthTables* pShiftToDepth, + const XnShiftToDepthConfig* pConfig); + +XnStatus XnShiftToDepthConvert(const XnShiftToDepthTables* pShiftToDepth, + const XnUInt16* pInput, + XnUInt32 nInputSize, + OniDepthPixel* pOutput); + +XnStatus XnShiftToDepthFree(XnShiftToDepthTables* pShiftToDepth); + +#endif //_XN_SHIFT_TO_DEPTH_H_ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.cpp new file mode 100644 index 0000000..a1470b1 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.cpp @@ -0,0 +1,460 @@ +#include "XnSocketConnectionFactory.h" +#include "XnSyncSocketConnection.h" +#include "XnSocketInConnection.h" +#include "XnServerSocketInConnection.h" +#include "XnClientSocketInConnection.h" +#include +#include +#include + +#define XN_MASK_SOCKETS "xnSockets" + +namespace xn +{ + +#define PRIME_CLIENT_INSTALL_PATH_ENV "PRIME_CLIENT_INSTALL_PATH" +#define PRIME_CLIENT_CONFIG_FILE "PrimeClient.ini" + + + +const XnUInt16 SocketConnectionFactory::CONTROL_MAX_PACKET_SIZE = 65535; +const XnUInt16 SocketConnectionFactory::NUM_SERVER_TO_CLIENT_DATA_CONNECTIONS = 1; +const XnUInt16 SocketConnectionFactory::DATA_OUT_MAX_PACKET_SIZE = 65535; +const XnUInt16 SocketConnectionFactory::DATA_IN_MAX_PACKET_SIZE = 65535; +xnl::Array SocketConnectionFactory::s_enumerationTargets; + +xnl::Array SocketConnectionFactory::s_controlConnections; + +SocketConnectionFactory::SocketConnectionFactory(Type type) +{ + xnOSMemSet(m_strIP, 0, sizeof(m_strIP)); + m_nControlPort = 0; + m_nDataOutPort = 0; + m_nDataInBasePort = 0; + m_bInitialized = FALSE; + m_type = type; +} + +SocketConnectionFactory::~SocketConnectionFactory() +{ + Shutdown(); +} + +XnStatus SocketConnectionFactory::Init(const XnChar* strConnString) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = xnOSInitNetwork(); + XN_IS_STATUS_OK_LOG_ERROR("Init network", nRetVal); + nRetVal = ParseConnectionString(strConnString, m_strIP, sizeof(m_strIP), m_nControlPort); + XN_IS_STATUS_OK_LOG_ERROR("Parse connection string", nRetVal); + + /* The ports are always arranged in the following order: + - Control Port + - Client to server data port + - Server to client data port number 1 + - Server to client data port number 2 + . + . + . + - Server to client data port number NUM_SERVER_TO_CLIENT_DATA_CONNECTIONS + */ + if (m_type == TYPE_SERVER) + { + m_nDataInBasePort = (m_nControlPort + 1); + m_nDataOutPort = (m_nDataInBasePort + 1); + + // Initialize listener + nRetVal = m_serverListener.Init(m_strIP, + m_nControlPort, + m_nControlPort + 1, + m_nControlPort + 2, + NUM_SERVER_TO_CLIENT_DATA_CONNECTIONS, + CONTROL_MAX_PACKET_SIZE, + DATA_OUT_MAX_PACKET_SIZE, + DATA_IN_MAX_PACKET_SIZE); + + if (nRetVal != XN_STATUS_OK) + { + return nRetVal; + } + } + else + { + m_nDataOutPort = (m_nControlPort + 1); + + //0x81 is first input port in USB - we want first input port in sockets to be 20002 + m_nDataInBasePort = m_nDataOutPort + 1; + } + + m_bInitialized = TRUE; + return XN_STATUS_OK; +} + +void SocketConnectionFactory::Shutdown() +{ + m_bInitialized = FALSE; + + if (m_type == TYPE_SERVER) + { + m_serverListener.Shutdown(); + } +} + +XnBool SocketConnectionFactory::IsInitialized() const +{ + return m_bInitialized; +} + +XnUInt16 SocketConnectionFactory::GetNumInputDataConnections() const +{ + if (m_type == TYPE_SERVER) + { + return 1; + } + else + { + return NUM_SERVER_TO_CLIENT_DATA_CONNECTIONS; + } +} + +XnUInt16 SocketConnectionFactory::GetNumOutputDataConnections() const +{ + if (m_type == TYPE_SERVER) + { + return NUM_SERVER_TO_CLIENT_DATA_CONNECTIONS; + } + else + { + return 1; + } +} + + +XnStatus SocketConnectionFactory::GetControlConnection(ISyncIOConnection*& pConn) +{ + SyncSocketConnection* pSyncSocketConn = NULL; + XnStatus nRetVal = XN_STATUS_OK; + + if (m_type == TYPE_CLIENT) + { + nRetVal = GetControlConnectionImpl(m_strIP, m_nControlPort, pSyncSocketConn); + XN_IS_STATUS_OK_LOG_ERROR("Get client control connection", nRetVal); + } + else + { + return m_serverListener.GetControlConnection(pConn); + } + + pConn = pSyncSocketConn; + return XN_STATUS_OK; +} + +XnStatus SocketConnectionFactory::CreateOutputDataConnection(XnUInt16 nID, IOutputConnection*& pConn) +{ + if (!m_bInitialized) + { + return XN_STATUS_NOT_INIT; + } + SyncSocketConnection* pSyncSocketConnection = NULL; + if (m_type == TYPE_SERVER) + { + // We do not create a new connection, we accept a connection from the listener. + return m_serverListener.CreateOutputDataConnection(nID, pConn); + } + else + { + //TODO: Change SyncSocketConnection to ClientSyncSocketConnection + pSyncSocketConnection = XN_NEW(SyncSocketConnection); + } + + XN_VALIDATE_ALLOC_PTR(pSyncSocketConnection); + XnStatus nRetVal = pSyncSocketConnection->Init(m_strIP, m_nDataOutPort, DATA_OUT_MAX_PACKET_SIZE); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_SOCKETS, "Initialize output data socket for ip '%s', port %u: %s", + m_strIP, m_nDataOutPort, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + XN_DELETE(pSyncSocketConnection); + return nRetVal; + } + + pConn = pSyncSocketConnection; + return XN_STATUS_OK; +} + +XnStatus SocketConnectionFactory::CreateInputDataConnection(XnUInt16 nID, IAsyncInputConnection*& pConn) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bInitialized) + { + return XN_STATUS_NOT_INIT; + } + SocketInConnection* pInSocketConn = NULL; + if (m_type == TYPE_SERVER) + { + // We do not create a new connection, we accept a connection from the listener. + // Only one input data connection in server, so nID is not used + return m_serverListener.CreateInputDataConnection(pConn); + } + else + { + pInSocketConn = XN_NEW(ClientSocketInConnection); + } + XN_VALIDATE_ALLOC_PTR(pInSocketConn); + + nRetVal = pInSocketConn->Init(m_strIP, m_nDataInBasePort + nID, DATA_IN_MAX_PACKET_SIZE); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_SOCKETS, "Initialize input data socket for ip '%s', port %u: %s", + m_strIP, m_nDataInBasePort + nID, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + XN_DELETE(pInSocketConn); + return nRetVal; + } + + pConn = pInSocketConn; + return XN_STATUS_OK; +} + +XnStatus SocketConnectionFactory::AddEnumerationTarget(const XnChar* strConnString) +{ + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = s_enumerationTargets.SetSize(s_enumerationTargets.GetSize()+1); + XN_IS_STATUS_OK_LOG_ERROR("Add to enumeration targets", nRetVal); + nRetVal = xnOSStrCopy(s_enumerationTargets[s_enumerationTargets.GetSize()-1].m_strConn, + strConnString, sizeof(XnConnectionString)); + XN_IS_STATUS_OK_LOG_ERROR("Copy connection string", nRetVal); + return XN_STATUS_OK; +} + +XnStatus SocketConnectionFactory::TryAndAddEnumerationTarget(xnl::Array& result, const XnChar* strConnString) +{ + XnStatus nRetVal = XN_STATUS_OK; + SyncSocketConnection* pConnection = NULL; + XnChar strIP[XN_FILE_MAX_PATH]; + XnUInt16 nPort = 0; + nRetVal = ParseConnectionString(strConnString, strIP, sizeof(strIP), nPort); + XN_IS_STATUS_OK_LOG_ERROR("Parse connection string", nRetVal); + + nRetVal = GetControlConnectionImpl(strIP, nPort, pConnection); + XN_IS_STATUS_OK_LOG_ERROR("Get control connection", nRetVal); + + nRetVal = pConnection->Connect(); + if (nRetVal == XN_STATUS_OK) + { + ConnectionStringStruct connString; + EncodeConnectionString(connString.m_strConn, sizeof(connString.m_strConn), strIP, nPort); + + // make sure we have space + nRetVal = result.AddLast(connString); + XN_IS_STATUS_OK(nRetVal); + } + else + { + xnLogInfo(XN_MASK_SOCKETS, "Couldn't connect to %s:%u - '%s'", strIP, nPort, xnGetStatusString(nRetVal)); + } + + return (XN_STATUS_OK); +} + +XnStatus SocketConnectionFactory::AddConfigFileTarget(xnl::Array& result, XnUInt16 nProductID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnChar strConfigFile[XN_FILE_MAX_PATH]; + nRetVal = xnOSGetEnvironmentVariable(PRIME_CLIENT_INSTALL_PATH_ENV, strConfigFile, XN_FILE_MAX_PATH); + if (nRetVal == XN_STATUS_OK) + { + nRetVal = xnOSStrAppend(strConfigFile, "/Config/", XN_FILE_MAX_PATH); + XN_IS_STATUS_OK(nRetVal); + } + else if (nRetVal == XN_STATUS_OS_ENV_VAR_NOT_FOUND) + { + nRetVal = xnOSStrCopy(strConfigFile, "./", sizeof(strConfigFile)); + XN_IS_STATUS_OK(nRetVal); + } + else + { + return (nRetVal); + } + + nRetVal = xnOSStrAppend(strConfigFile, PRIME_CLIENT_CONFIG_FILE, XN_FILE_MAX_PATH); + XN_IS_STATUS_OK(nRetVal); + + XnBool bDoesConfigExist = FALSE; + nRetVal = xnOSDoesFileExist(strConfigFile, &bDoesConfigExist); + XN_IS_STATUS_OK(nRetVal); + + if (!bDoesConfigExist) + { + // no file + return (XN_STATUS_OK); + } + + // file exist. read IP address and port from it + XnChar strProductID[80]; + sprintf(strProductID, "%04X", nProductID); + + XnChar strIP[XN_FILE_MAX_PATH]; + nRetVal = xnOSReadStringFromINI(strConfigFile, strProductID, "IPAddress", strIP, sizeof(strIP)); + if (nRetVal != XN_STATUS_OK) + { + // not found. nothing to connect to + return (XN_STATUS_OK); + } + + XnInt32 nPort = 0; + nRetVal = xnOSReadIntFromINI(strConfigFile, strProductID, "Port", &nPort); + if (nRetVal != XN_STATUS_OK) + { + // not found. nothing to connect to + return (XN_STATUS_OK); + } + + XnConnectionString strConnString; + nRetVal = EncodeConnectionString(strConnString, sizeof(strConnString), strIP, XnUInt16(nPort)); + XN_IS_STATUS_OK_LOG_ERROR("Encode connection string", nRetVal); + nRetVal = TryAndAddEnumerationTarget(result, strConnString); + XN_IS_STATUS_OK(nRetVal); + + return (XN_STATUS_OK); +} + +XnStatus SocketConnectionFactory::EnumerateConnStrings(XnUInt16 nProductID, + XnConnectionString*& astrConnStrings, + XnUInt32& nCount) +{ + XnStatus nRetVal = XN_STATUS_OK; + + astrConnStrings = NULL; + nCount = 0; + + nRetVal = xnOSInitNetwork(); + XN_IS_STATUS_OK_LOG_ERROR("Init network", nRetVal); + + // Enumeration targets can be received from 2 sources: + // 1. via the AddEnumerationTarget() method. + // 2. from config file. + xnl::Array result; + + for (XnUInt32 i = 0; i < s_enumerationTargets.GetSize(); ++i) + { + nRetVal = TryAndAddEnumerationTarget(result, s_enumerationTargets[i].m_strConn); + XN_IS_STATUS_OK(nRetVal); + } + + // add from config file + nRetVal = AddConfigFileTarget(result, nProductID); + XN_IS_STATUS_OK(nRetVal); + + astrConnStrings = (XnConnectionString*)xnOSCalloc(result.GetSize(), sizeof(astrConnStrings[0])); + XN_VALIDATE_ALLOC_PTR(astrConnStrings); + + for (XnUInt32 i = 0; i < result.GetSize(); ++i) + { + nRetVal = xnOSStrCopy(astrConnStrings[i], result[i].m_strConn, sizeof(astrConnStrings[i])); + XN_IS_STATUS_OK(nRetVal); + } + + nCount = result.GetSize(); + + return XN_STATUS_OK; +} + +void SocketConnectionFactory::FreeConnStringsList(XnConnectionString* astrConnStrings) +{ + xnOSFree(astrConnStrings); +} + +XnStatus SocketConnectionFactory::GetControlConnectionImpl(const XnChar* strIP, + XnUInt16 nPort, + SyncSocketConnection*& pControlConnection) +{ + XnStatus nRetVal = XN_STATUS_OK; + pControlConnection = NULL; + + //Try and find existing connection + for (XnUInt32 i = 0; i < s_controlConnections.GetSize(); i++) + { + if ((xnOSStrCmp(s_controlConnections[i].GetIP(), strIP) == 0) && + (s_controlConnections[i].GetPort() == nPort)) + { + pControlConnection = &s_controlConnections[i]; + break; + } + } + + if (pControlConnection == NULL) + { + //Control connection is not in array - add it + nRetVal = s_controlConnections.SetSize(s_controlConnections.GetSize() + 1); + XN_IS_STATUS_OK_LOG_ERROR("Add to control connections array", nRetVal); + pControlConnection = &s_controlConnections[s_controlConnections.GetSize() - 1]; + } + + if (!pControlConnection->IsInitialized()) + { + //Control connection it not initialized - initialize it + /*TODO: Change XN_CONTROL_PREDEFINED_MAX_PACKET_SIZE to variable once we have service discovery that + tells us the control endpoint packet size in LinkControlEndpoint. */ + nRetVal = pControlConnection->Init(strIP, nPort, CONTROL_MAX_PACKET_SIZE); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_SOCKETS, "Failed to initialize control socket for ip '%s', port %u: %s", + strIP, nPort, xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + pControlConnection = NULL; + return nRetVal; + } + } + + return XN_STATUS_OK; +} + +XnStatus SocketConnectionFactory::ParseConnectionString(const XnChar* strConnString, + XnChar* strIP, + XnUInt32 nIPBufSize, + XnUInt16& nPort) +{ + XnStatus nRetVal = XN_STATUS_OK; + const XnChar* pColon = NULL; + const XnChar* strPort = NULL; + XnUInt32 nTempPort = 0; + + pColon = strchr(strConnString, ':'); + if (pColon == NULL) + { + xnLogError(XN_MASK_SOCKETS, "Invalid connection string - missing ':'."); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + //Take IP address from beginning of connection string + nRetVal = xnOSStrNCopy(strIP, strConnString, XnUInt32(pColon - strConnString), nIPBufSize); + XN_IS_STATUS_OK_LOG_ERROR("Copy IP address", nRetVal); + strIP[pColon - strConnString] = '\0'; + //Take port number from chars after colon + strPort = (pColon + 1); + nTempPort = atoi(strPort); + if ((nTempPort == 0) || (nTempPort > XN_MAX_UINT16)) + { + xnLogError(XN_MASK_SOCKETS, "Invalid connection string - bad port number %u", nTempPort); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + nPort = XnUInt16(nTempPort); + return XN_STATUS_OK; +} + +XnStatus SocketConnectionFactory::EncodeConnectionString(XnChar* strConnString, XnUInt32 nBufferSize, const XnChar* strIP, XnUInt16 nPort) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nCharsWritten = 0; + nRetVal = xnOSStrFormat(strConnString, nBufferSize, &nCharsWritten, "%s:%u", strIP, nPort); + XN_IS_STATUS_OK_LOG_ERROR("Format connection string", nRetVal); + return XN_STATUS_OK; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.h b/Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.h new file mode 100644 index 0000000..3b34c2b --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSocketConnectionFactory.h @@ -0,0 +1,78 @@ +#ifndef __SOCKETCONNECTIONFACTORY_H__ +#define __SOCKETCONNECTIONFACTORY_H__ + +#include "IConnectionFactory.h" +#include "XnSyncSocketConnection.h" +#include "XnSyncServerSocketConnection.h" + +#include + +namespace xn +{ + +class SocketConnectionFactory: public IConnectionFactory +{ +public: + enum Type {TYPE_CLIENT, TYPE_SERVER}; + SocketConnectionFactory(Type type); + virtual ~SocketConnectionFactory(); + virtual XnStatus Init(const XnChar* strConnString); + virtual void Shutdown(); + virtual XnBool IsInitialized() const; + virtual XnUInt16 GetNumInputDataConnections() const; + virtual XnUInt16 GetNumOutputDataConnections() const; + + /** The pointer returned by GetControlConnection() belongs to the connection factory and + must not be deleted by caller. **/ + virtual XnStatus GetControlConnection(ISyncIOConnection*& pConnection); + + // nID is used for server, as the server has several output connections. + virtual XnStatus CreateOutputDataConnection(XnUInt16 nID, IOutputConnection*& pConnection); + + // nID is used for client, as the client has several output connections. + virtual XnStatus CreateInputDataConnection(XnUInt16 nID, IAsyncInputConnection*& pConnection); + + static XnStatus AddEnumerationTarget(const XnChar* strConnString); + + static XnStatus EnumerateConnStrings(XnUInt16 nProductID, XnConnectionString*& astrConnStrings, XnUInt32& nCount); + static void FreeConnStringsList(XnConnectionString* astrConnStrings); + + static const XnUInt16 DATA_OUT_MAX_PACKET_SIZE; + static const XnUInt16 DATA_IN_MAX_PACKET_SIZE; + +private: + static const XnUInt16 CONTROL_MAX_PACKET_SIZE; + + static XnStatus ParseConnectionString(const XnChar* strConnString, XnChar* strIP, XnUInt32 nIPBufSize, XnUInt16& nPort); + static XnStatus EncodeConnectionString(XnChar* strConnString, XnUInt32 nBufferSize, const XnChar* strIP, XnUInt16 nPort); + + // patch: we need to define a struct to keep in XnArray (XnArray cannot work directly with XnConnectionString) + typedef struct ConnectionStringStruct + { + XnConnectionString m_strConn; + } ConnectionStringStruct; + + static const XnUInt16 NUM_SERVER_TO_CLIENT_DATA_CONNECTIONS; + static xnl::Array s_controlConnections; + static XnStatus GetControlConnectionImpl(const XnChar* strIP, XnUInt16 nPort, SyncSocketConnection*& pControlConnection); + static XnStatus TryAndAddEnumerationTarget(xnl::Array& result, const XnChar* strConnString); + static XnStatus AddConfigFileTarget(xnl::Array& result, XnUInt16 nProductID); + + // The client implementation returns ISyncIOConnection object that could not be deleted (return from s_controlConnections), + // I want the behavior to be consistent for both client and server. + SyncServerSocketListener m_serverListener; + + Type m_type; + XnChar m_strIP[XN_FILE_MAX_PATH]; + XnUInt16 m_nControlPort; + XnUInt16 m_nDataOutPort; + XnUInt16 m_nDataInBasePort; + XnBool m_bInitialized; + + static xnl::Array s_enumerationTargets; +}; + +} + +#endif // __SOCKETCONNECTIONFACTORY_H__ + diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.cpp new file mode 100644 index 0000000..754cb0d --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.cpp @@ -0,0 +1,282 @@ +#include "XnSocketInConnection.h" +#include "XnLinkProto.h" +#include "XnLinkProtoUtils.h" +#include "XnLinkDefs.h" +#include +#include + +namespace xn +{ + +//const XnUInt32 SocketInConnection::BUFFER_NUM_PACKETS = 8*12; +const XnUInt32 SocketInConnection::BUFFER_NUM_PACKETS = 1; +const XnUInt32 SocketInConnection::RECEIVE_TIMEOUT = 50; + +const XnUInt32 SocketInConnection::CONNECT_TIMEOUT = 10000; +const XnUInt32 SocketInConnection::READ_THREAD_TERMINATE_TIMEOUT = 10000; + +//TEMP TEMP TEMP +//const XnUInt32 SocketInConnection::CONNECT_TIMEOUT = XN_WAIT_INFINITE; +//const XnUInt32 SocketInConnection::READ_THREAD_TERMINATE_TIMEOUT = XN_WAIT_INFINITE; +//TEMP TEMP TEMP + +SocketInConnection::SocketInConnection() +{ + xnOSMemSet(m_strIP, 0, sizeof(m_strIP)); + m_nPort = 0; + m_hReadThread = NULL; + m_hConnectEvent = NULL; + m_bStopReadThread = FALSE; + m_pDataDestination = NULL; + m_nBufferSize = 0; + m_nMaxPacketSize = 0; + m_nConnectionStatus = XN_STATUS_OS_NETWORK_CONNECTION_CLOSED; + m_pBuffer = NULL; +} + +SocketInConnection::~SocketInConnection() +{ + Shutdown(); +} + +XnStatus SocketInConnection::Init(const XnChar* strIP, XnUInt16 nPort, XnUInt16 nMaxPacketSize) +{ + XN_VALIDATE_INPUT_PTR(strIP); + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = xnOSStrCopy(m_strIP, strIP, sizeof(m_strIP)); + XN_IS_STATUS_OK_LOG_ERROR("Copy IP", nRetVal); + m_nPort = nPort; + m_nMaxPacketSize = nMaxPacketSize; + m_nBufferSize = m_nMaxPacketSize * BUFFER_NUM_PACKETS; + m_pBuffer = reinterpret_cast(xnOSMallocAligned(m_nBufferSize, XN_DEFAULT_MEM_ALIGN)); + XN_VALIDATE_ALLOC_PTR(m_pBuffer); + nRetVal = xnOSCreateEvent(&m_hConnectEvent, FALSE); + XN_IS_STATUS_OK_LOG_ERROR("Create event", nRetVal); + xnLogVerbose(XN_MASK_LINK, "Event created for socket %u", m_nPort); + return XN_STATUS_OK; +} + +void SocketInConnection::Shutdown() +{ + xnLogVerbose(XN_MASK_LINK, "Socket in connection %u shutting down", m_nPort); + Disconnect(); + xnOSFreeAligned(m_pBuffer); + m_pBuffer = NULL; + xnOSCloseEvent(&m_hConnectEvent); +} + +XnStatus SocketInConnection::Connect() +{ + XnStatus nRetVal = XN_STATUS_OK; + Disconnect(); // In case we're already connected + nRetVal = xnOSCreateThread(&ReadThreadProc, this, &m_hReadThread); + XN_IS_STATUS_OK_LOG_ERROR("Create input socket read thread", nRetVal); + xnLogVerbose(XN_MASK_LINK, "Waiting for connection on socket %u...", m_nPort); + nRetVal = xnOSWaitEvent(m_hConnectEvent, CONNECT_TIMEOUT); + XN_IS_STATUS_OK_LOG_ERROR("Wait for input socket to connect", nRetVal); + if (m_nConnectionStatus != XN_STATUS_OK) + { + xnLogError(XN_MASK_LINK, "Failed to connect to socket %u: %s", m_nPort, xnGetStatusString(m_nConnectionStatus)); + XN_ASSERT(FALSE); + return m_nConnectionStatus; + } + xnLogVerbose(XN_MASK_LINK, "Socket %u connected.", m_nPort); + nRetVal = xnOSSetThreadPriority(m_hReadThread, XN_PRIORITY_CRITICAL); + XN_IS_STATUS_OK_LOG_ERROR("Set read thread priority", nRetVal); + return XN_STATUS_OK; +} + +XnBool SocketInConnection::IsConnected() const +{ + return (m_nConnectionStatus == XN_STATUS_OK); +} + +void SocketInConnection::Disconnect() +{ + XnStatus nRetVal = XN_STATUS_OK; + if (m_hReadThread != NULL) + { + m_bStopReadThread = TRUE; //Signal read thread to stop running + nRetVal = xnOSWaitAndTerminateThread(&m_hReadThread, READ_THREAD_TERMINATE_TIMEOUT); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning("Failed to terminate input socket read thread: %s", xnGetStatusString(nRetVal)); + XN_ASSERT(FALSE); + } + m_bStopReadThread = FALSE; + } +} + + +XnUInt16 SocketInConnection::GetMaxPacketSize() const +{ + return m_nMaxPacketSize; +} + +XnStatus SocketInConnection::SetDataDestination(IDataDestination* pDataDestination) +{ + m_pDataDestination = pDataDestination; + return XN_STATUS_OK; +} + +XN_THREAD_PROC SocketInConnection::ReadThreadProc(XN_THREAD_PARAM pThreadParam) +{ + SocketInConnection* pThis = reinterpret_cast(pThreadParam); + if (pThis == NULL) + { + xnLogError(XN_MASK_LINK, "Got NULL in socket read thread param :("); + XN_ASSERT(FALSE); + return NULL; + } + + pThis->ReadThreadProcImpl(); + + XN_THREAD_PROC_RETURN(0); +} + +XnStatus SocketInConnection::ReadThreadProcImpl() +{ + XnStatus nRetVal = XN_STATUS_OK; + XN_SOCKET_HANDLE hSocket = NULL; + XnBool bCanceled = FALSE; + XnUInt32 nPacketBytesRead = 0; + XnUInt32 nTotalBytesRead = 0; + + m_nConnectionStatus = ConnectSocket(hSocket, m_strIP, m_nPort); + XN_IS_STATUS_OK_LOG_ERROR("Connect socket", m_nConnectionStatus); + nRetVal = xnOSSetEvent(m_hConnectEvent); + XN_IS_STATUS_OK_LOG_ERROR("Set connect event", nRetVal); + + while (!m_bStopReadThread) + { + //Fill buffer with received packets + nTotalBytesRead = 0; + for (XnUInt32 nPacket = 0; (nPacket < BUFFER_NUM_PACKETS); nPacket++) + { + nPacketBytesRead = m_nMaxPacketSize; + m_nConnectionStatus = ReceivePacket(hSocket, m_pBuffer + nTotalBytesRead, nPacketBytesRead, bCanceled); + if (m_nConnectionStatus != XN_STATUS_OK) + { + m_pDataDestination->HandleDisconnection(); + xnLogError(XN_MASK_LINK, "Failed to receive packet: %s", xnGetStatusString(m_nConnectionStatus)); + //XN_ASSERT(FALSE); + return m_nConnectionStatus; + } + + if (bCanceled) + { + //Ignore packet and exit loop + break; + } + + if (nTotalBytesRead == m_nBufferSize) + { + xnLogError(XN_MASK_LINK, "Read thread buffer overflowed :("); + XN_ASSERT(FALSE); + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + + nTotalBytesRead += nPacketBytesRead; + } + + if (m_pDataDestination != NULL) + { + //Send data in buffer to its destination. + //Even if at this point the read thread should be stopped, first we send all the complete packets we got. + if (nTotalBytesRead > 0) + { + m_pDataDestination->IncomingData(m_pBuffer, nTotalBytesRead); + } + } + } + + nRetVal = xnOSCloseSocket(hSocket); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_LINK, "Failed to close input data socket :("); + XN_ASSERT(FALSE); + } + m_nConnectionStatus = XN_STATUS_OS_NETWORK_CONNECTION_CLOSED; + + return XN_STATUS_OK; +} + +XnStatus SocketInConnection::ReceivePacket(XN_SOCKET_HANDLE hSocket, void* pDestBuffer, XnUInt32& nSize, XnBool& bCanceled) +{ + XnStatus nRetVal = XN_STATUS_OK; + LinkPacketHeader* pPacket = reinterpret_cast(pDestBuffer); + + XN_ASSERT(nSize >= sizeof(LinkPacketHeader)); + /* We first receive the packet's header to know its size, and then receive exactly as many bytes as needed. + If we just received max packet size, we might overrun a smaller packet and receive part of the next packet. + (We don't have this problem with USB cuz we always get a whole packet there).*/ + + nRetVal = ReceiveExactly(hSocket, pPacket, sizeof(LinkPacketHeader), bCanceled); + if (bCanceled) + { + //The request to receive a packet was canceled + return XN_STATUS_OK; + } + //XN_IS_STATUS_OK_LOG_ERROR("Receive packet header", nRetVal); + XN_IS_STATUS_OK(nRetVal); + + if (!pPacket->IsMagicValid()) + { + xnLogError(XN_MASK_LINK, "Got bad link packet header magic :("); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + XnUInt16 nPacketSize = pPacket->GetSize(); + if (nSize < nPacketSize) + { + xnLogError(XN_MASK_LINK, "Insufficient buffer (%u bytes) to hold packet of %u bytes", nSize, nPacketSize); + XN_ASSERT(FALSE); + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + nSize = 0; //In case we get canceled + nRetVal = ReceiveExactly(hSocket, pPacket->GetPacketData(), nPacketSize - sizeof(LinkPacketHeader), bCanceled); + XN_IS_STATUS_OK_LOG_ERROR("Receive packet body", nRetVal); + if (bCanceled) + { + //The request to receive a packet was canceled + return XN_STATUS_OK; + } + nSize = nPacketSize; + + return XN_STATUS_OK; +} + +XnStatus SocketInConnection::ReceiveExactly(XN_SOCKET_HANDLE hSocket, void* pDestBuffer, XnUInt32 nSize, XnBool& bCanceled) +{ + XnStatus nRetVal = XN_STATUS_OK; + XnUInt32 nTotalBytesReceived = 0; + XnUInt32 nIterationBytesReceived = 0; + bCanceled = FALSE; + while ((nTotalBytesReceived < nSize) && (!m_bStopReadThread)) + { + nIterationBytesReceived = (nSize - nTotalBytesReceived); + nRetVal = xnOSReceiveNetworkBuffer(hSocket, ((XnChar*)pDestBuffer) + nTotalBytesReceived, &nIterationBytesReceived, RECEIVE_TIMEOUT); + if (nRetVal == XN_STATUS_OS_NETWORK_TIMEOUT) + { + //No data, no problem + continue; + } + /*else if (nRetVal == XN_STATUS_OS_NETWORK_CONNECTION_CLOSED) + { + //This is ok - same as cancel + break; + }*/ + XN_IS_STATUS_OK(nRetVal); + nTotalBytesReceived += nIterationBytesReceived; + } + + if (nTotalBytesReceived < nSize) + { + //We didn't get all the data we expected - we were canceled. + bCanceled = TRUE; + } + + return nRetVal; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.h b/Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.h new file mode 100644 index 0000000..dcc2c52 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSocketInConnection.h @@ -0,0 +1,55 @@ +#ifndef __XNSOCKETINCONNECTION_H__ +#define __XNSOCKETINCONNECTION_H__ + +#include "IAsyncInputConnection.h" +#include "XnLinkProtoLibDefs.h" +#include + +namespace xn +{ + +class SocketInConnection : public IAsyncInputConnection +{ +public: + SocketInConnection(); + virtual ~SocketInConnection(); + virtual XnStatus Init(const XnChar* strIP, XnUInt16 nPort, XnUInt16 nMaxPacketSize); + virtual void Shutdown(); + virtual XnStatus Connect(); + virtual XnBool IsConnected() const; + virtual void Disconnect(); + virtual XnUInt16 GetMaxPacketSize() const; + //pDataDestination may be NULL and then data is not sent + virtual XnStatus SetDataDestination(IDataDestination* pDataDestination); + +protected: + static const XnUInt32 BUFFER_NUM_PACKETS; + static const XnUInt32 RECEIVE_TIMEOUT; + static const XnUInt32 CONNECT_TIMEOUT; + static const XnUInt32 READ_THREAD_TERMINATE_TIMEOUT; + + virtual XnStatus ConnectSocket(XN_SOCKET_HANDLE& hSocket, const XnChar* strIP, XnUInt16 nPort) = 0; + +protected: + static XN_THREAD_PROC ReadThreadProc(XN_THREAD_PARAM pThreadParam); + XnStatus ReadThreadProcImpl(); + //nSize is max size on input, actual size on output + XnStatus ReceivePacket(XN_SOCKET_HANDLE hSocket, void* pDestBuffer, XnUInt32& nSize, XnBool& bCanceled); + XnStatus ReceiveExactly(XN_SOCKET_HANDLE hSocket, void* pDestBuffer, XnUInt32 nSize, XnBool& bCanceled); + + + XnConnectionString m_strIP; + XnUInt16 m_nPort; + XnUInt16 m_nMaxPacketSize; + XN_THREAD_HANDLE m_hReadThread; + XN_EVENT_HANDLE m_hConnectEvent; //This event signals we passed the actual connection command (and will be set even if connection failed) + volatile XnBool m_bStopReadThread; //This is used to signal the read thread to stop + IDataDestination* m_pDataDestination; + XnUInt8* m_pBuffer; + XnUInt32 m_nBufferSize; + volatile XnStatus m_nConnectionStatus; +}; + +} + +#endif // __XNSOCKETINCONNECTION_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.cpp new file mode 100644 index 0000000..8a483e4 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.cpp @@ -0,0 +1,234 @@ +// XnSyncServerSocketConnection.cpp + + +#include "XnSyncServerSocketConnection.h" +#include + +namespace xn +{ + +SyncServerSocketListener::SyncServerSocketListener() +{ + // Nullify all sockets + m_hListenControlSocket = NULL; + m_hListenDataInSocket = NULL; + m_nDataOutSockets = 0; + m_nMaxControlPacketSize = 0; + m_nMaxDataOutPacketSize = 0; + m_nMaxDataInPacketSize = 0; + + for (XnUInt32 nDataOutSocket = 0; nDataOutSocket < MAX_DATAOUT_SOCKETS; nDataOutSocket++) + { + m_arrhListenDataOutSockets[nDataOutSocket] = NULL; + } + + // Init control connection pool + for (XnUInt32 nConn = 0; nConn < MAX_SERVER_CONTROL_CONNECTIONS; nConn++) + { + m_controlConnPool[nConn].fActive = false; + } +} + +SyncServerSocketListener::~SyncServerSocketListener() +{ + if (m_hListenControlSocket || m_hListenDataInSocket) + { + Shutdown(); + } +} + +XnStatus SyncServerSocketListener::Init(const XnChar* strIP, + XnUInt16 nControlPort, + XnUInt16 nInputPort, + XnUInt16 nFirstDataPort, + XnUInt16 nDataOutSockets, + XnUInt16 nMaxColntrolPacketSize, + XnUInt16 nMaxDataOutPacketSize, + XnUInt16 nMaxDataInPacketSize) +{ + XnStatus rc = XN_STATUS_OK; + + XN_ASSERT(!m_hListenControlSocket); + XN_ASSERT(!m_hListenDataInSocket); + + XN_ASSERT(nDataOutSockets < MAX_DATAOUT_SOCKETS); + + m_nMaxControlPacketSize = nMaxColntrolPacketSize; + m_nMaxDataOutPacketSize = nMaxDataOutPacketSize; + m_nMaxDataInPacketSize = nMaxDataInPacketSize; + m_nDataOutSockets = nDataOutSockets; + + const XnUInt16 NON_DATA_OUT_CONNECTIONS = 2; + XnUInt16 nSocketsToCreate = NON_DATA_OUT_CONNECTIONS + nDataOutSockets; // Control + + All data outs + + for (XnUInt16 nSockToCreate = 0; nSockToCreate < nSocketsToCreate; nSockToCreate++) + { + XN_SOCKET_HANDLE newSocket = NULL; + XnUInt16 nCurrSocketPort = nSockToCreate == 0 ? nControlPort : // First create control socket + nSockToCreate == 1 ? nInputPort : // Input port + nFirstDataPort + (nSockToCreate - NON_DATA_OUT_CONNECTIONS); // Output ports + + // Create listen socket for control connections: + rc = xnOSCreateSocket(XN_OS_TCP_SOCKET, strIP, nCurrSocketPort, &newSocket); + if (rc != XN_STATUS_OK) + { + Shutdown();break; + } + rc = xnOSBindSocket(newSocket); + if (rc != XN_STATUS_OK) + { + Shutdown();break; + } + rc = xnOSListenSocket(newSocket); + if (rc != XN_STATUS_OK) + { + Shutdown();break; + } + + // Store the socket + if (nSockToCreate == 0) m_hListenControlSocket = newSocket; + else if (nSockToCreate == 1) m_hListenDataInSocket = newSocket; + else + { + XnUInt32 nOutSocketIndex = nSockToCreate - NON_DATA_OUT_CONNECTIONS; + XN_ASSERT(nOutSocketIndex < MAX_DATAOUT_SOCKETS); + m_arrhListenDataOutSockets[nOutSocketIndex] = newSocket; + } + } + + // Set the timeout for the control connection to something low enough to allow many clients on one thread + SyncSocketConnection::RECEIVE_TIMEOUT = 50; + + return rc; +} + +void SyncServerSocketListener::Shutdown() +{ + if (m_hListenControlSocket) + { + xnOSCloseSocket(m_hListenControlSocket); + m_hListenControlSocket = NULL; + } + if (m_hListenDataInSocket) + { + xnOSCloseSocket(m_hListenDataInSocket); + m_hListenDataInSocket = NULL; + } + for (XnUInt32 nOutConn = 0; nOutConn < MAX_SERVER_CONTROL_CONNECTIONS; nOutConn++) + { + if (m_arrhListenDataOutSockets[nOutConn]) + { + xnOSCloseSocket(m_arrhListenDataOutSockets[nOutConn]); + m_arrhListenDataOutSockets[nOutConn] = NULL; + } + } +} + +// Returns SyncServerSocketConnection +XnStatus SyncServerSocketListener::GetControlConnection(ISyncIOConnection*& pStream) +{ + XN_SOCKET_HANDLE hClientSocket = NULL; + + // Find free connection and destroy terminated connections + const XnUInt32 FREECONN_NA = (XnUInt32)-1; + XnUInt32 nFreeConn = FREECONN_NA; + + for (XnUInt32 nConn = 0; nConn < MAX_SERVER_CONTROL_CONNECTIONS; nConn++) + { + PoolControlConnection& CurrConn = m_controlConnPool[nConn]; + + // Deactivate disconnected connections + if (CurrConn.fActive) + { + if (!CurrConn.syncConnection.IsConnected()) + { + CurrConn.fActive = false; + } + } + + // See if we can use this connection + if (!CurrConn.fActive && nFreeConn == FREECONN_NA) + { + nFreeConn = nConn; + } + } + + // See if we got inactive connection from the pool + if (nFreeConn == FREECONN_NA) + { + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + + // Accept the connection (blocking) + XnStatus rc = xnOSAcceptSocket(m_hListenControlSocket, &hClientSocket, XN_WAIT_INFINITE); + if (rc == XN_STATUS_OK) + { + XN_ASSERT(m_hListenControlSocket); + + PoolControlConnection& FreeConn = m_controlConnPool[nFreeConn]; + FreeConn.syncConnection.m_hSocket = hClientSocket; + FreeConn.syncConnection.m_nMaxPacketSize = m_nMaxControlPacketSize; + + FreeConn.fActive = true; + pStream = &FreeConn.syncConnection; + } + + return rc; + +} + +// Creates a NEW SyncServerSocketConnection +XnStatus SyncServerSocketListener::CreateOutputDataConnection(int nID, IOutputConnection*& pConn) +{ + XN_SOCKET_HANDLE hClientSocket = NULL; + + XN_ASSERT(nID < MAX_DATAOUT_SOCKETS); + XN_ASSERT(m_arrhListenDataOutSockets[nID]); + + // Accept the connection (blocking) + XnStatus rc = xnOSAcceptSocket(m_arrhListenDataOutSockets[nID], &hClientSocket, XN_WAIT_INFINITE); + if (rc == XN_STATUS_OK) + { + XN_ASSERT(hClientSocket); + + SyncServerSocketConnection* pSocket = XN_NEW(SyncServerSocketConnection); + pSocket->m_hSocket = hClientSocket; + pSocket->m_nMaxPacketSize = m_nMaxDataOutPacketSize; + pConn = pSocket; + } + + return rc; +} + +// Creates a NEW SyncServerSocketConnection +XnStatus SyncServerSocketListener::CreateInputDataConnection(IAsyncInputConnection*& pStream) +{ + XN_SOCKET_HANDLE hClientSocket = NULL; + + XN_ASSERT(m_hListenDataInSocket); + + // Accept the connection (blocking) + XnStatus rc = xnOSAcceptSocket(m_hListenDataInSocket, &hClientSocket, XN_WAIT_INFINITE); + if (rc == XN_STATUS_OK) + { + XN_ASSERT(hClientSocket); + + ASyncServerSocketConnection* pSocket = XN_NEW(ASyncServerSocketConnection); + pSocket->m_hSocketFromListener = hClientSocket; + + rc = pSocket->Init("", 0, m_nMaxDataInPacketSize); + if (rc != XN_STATUS_OK) + { + XN_DELETE(pSocket); + xnOSCloseSocket(hClientSocket); + } + + pStream = pSocket; + } + + return rc; +} + + + +} \ No newline at end of file diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.h b/Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.h new file mode 100644 index 0000000..961d744 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSyncServerSocketConnection.h @@ -0,0 +1,102 @@ +// XnSyncServerSocketConnection.h + +#ifndef _XNSYNCSERVERSOCKETCONNECTION_H_ +#define _XNSYNCSERVERSOCKETCONNECTION_H_ + +#include "XnSyncSocketConnection.h" +#include "XnStatusCodes.h" +#include "XnServerSocketInConnection.h" + + +namespace xn +{ + +class IAsyncInputConnection; + +class SyncServerSocketConnection : virtual public SyncSocketConnection +{ +public: + // Overriding connect, and init because this class is allready created with valid socket handle + virtual XnStatus Init(const XnChar* /*strIP*/, XnUInt16 /*nPort*/, XnUInt16 /*nMaxPacketSize*/) + { + return IsConnected() ? XN_STATUS_OK : XN_STATUS_ERROR; + } + virtual XnStatus Connect() + { + return IsConnected() ? XN_STATUS_OK : XN_STATUS_ERROR; + } + friend class SyncServerSocketListener; +}; + + +class ASyncServerSocketConnection : virtual public ServerSocketInConnection +{ +public: + virtual XnStatus ConnectSocket(XN_SOCKET_HANDLE& hSocket, const XnChar* /*strIP*/, XnUInt16 /*nPort*/) + { + hSocket = m_hSocketFromListener; + return XN_STATUS_OK; + } + +protected: + friend class SyncServerSocketListener; + XN_SOCKET_HANDLE m_hSocketFromListener; +}; + +#define MAX_SERVER_CONTROL_CONNECTIONS 10 +#define MAX_DATAOUT_SOCKETS 10 + +class SyncServerSocketListener +{ +public: + SyncServerSocketListener(); + virtual ~SyncServerSocketListener(); + + virtual XnStatus Init(const XnChar* strIP, + XnUInt16 nControlPort, + XnUInt16 nInputPort, + XnUInt16 nFirstOutputDataPort, + XnUInt16 nDataOutSockets, + XnUInt16 nMaxColntrolPacketSize, + XnUInt16 nMaxDataOutPacketSize, + XnUInt16 nMaxDataInPacketSize); + virtual void Shutdown(); + + // Returns internal SyncServerSocketConnection object, see note below + XnStatus GetControlConnection(ISyncIOConnection*& pStream); + + // Creates a NEW SyncServerSocketConnection + XnStatus CreateOutputDataConnection(int nID, IOutputConnection*& pConn); + + // Creates a NEW SyncServerSocketConnection + XnStatus CreateInputDataConnection(IAsyncInputConnection*& pStream); + + + +protected: + XN_SOCKET_HANDLE m_hListenControlSocket; + XN_SOCKET_HANDLE m_hListenDataInSocket; + XN_SOCKET_HANDLE m_arrhListenDataOutSockets[MAX_DATAOUT_SOCKETS]; + + XnUInt16 m_nDataOutSockets; + + XnUInt16 m_nMaxControlPacketSize; + XnUInt16 m_nMaxDataOutPacketSize; + XnUInt16 m_nMaxDataInPacketSize; + + + // The IConnectionFactory GetControlConnection function returns internal connections (not created with new), + // To use the same API for the server and the client, the server code for IConnectionFactory::GetControlConnection should reurn internal connection too. + // So here we use a connection pool + struct PoolControlConnection + { + XnBool fActive; + SyncServerSocketConnection syncConnection; + } m_controlConnPool[MAX_SERVER_CONTROL_CONNECTIONS]; + +}; + + +} + +#endif //_XNSYNCSERVERSOCKETCONNECTION_H_ diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.cpp b/Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.cpp new file mode 100644 index 0000000..37e7002 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.cpp @@ -0,0 +1,188 @@ +#include "XnSyncSocketConnection.h" +#include "XnLinkProto.h" +#include "XnLinkProtoUtils.h" +#include +#include + +#define XN_MASK_SYNC_SOCKET "xnSyncSocket" + +namespace xn +{ + +/*TODO: Check if CONNECT_TIMEOUT is enough for actual remote host... We probably + need to increase it, but that means enumeration will take longer. */ + +// Removed const to allow changes for the server +XnUInt32 SyncSocketConnection::CONNECT_TIMEOUT = XN_SOCKET_DEFAULT_TIMEOUT; +XnUInt32 SyncSocketConnection::RECEIVE_TIMEOUT = 35000; + +//TEMP TEMP TEMP +//const XnUInt32 SyncSocketConnection::CONNECT_TIMEOUT = XN_WAIT_INFINITE; +//const XnUInt32 SyncSocketConnection::RECEIVE_TIMEOUT = XN_WAIT_INFINITE; +//TEMP TEMP TEMP + +SyncSocketConnection::SyncSocketConnection() +{ + m_hSocket = NULL; + xnOSMemSet(m_strIP, 0, sizeof(m_strIP)); + m_nPort = 0; + m_nMaxPacketSize = 0; + m_bInitialized = FALSE; +} + +SyncSocketConnection::SyncSocketConnection(const SyncSocketConnection& other) +{ + *this = other; +} + +SyncSocketConnection::~SyncSocketConnection() +{ + Shutdown(); +} + +SyncSocketConnection& SyncSocketConnection::operator=(const SyncSocketConnection& other) +{ + xnOSStrCopy(m_strIP, other.m_strIP, sizeof(m_strIP)); + m_nPort = other.m_nPort; + m_hSocket = NULL; //We DON'T take the socket from the other connection + m_nMaxPacketSize = other.m_nMaxPacketSize; + return *this; +} + +XnStatus SyncSocketConnection::Init(const XnChar* strIP, XnUInt16 nPort, XnUInt16 nMaxPacketSize) +{ + Disconnect(); + XnStatus nRetVal = xnOSStrCopy(m_strIP, strIP, sizeof(m_strIP)); + XN_IS_STATUS_OK_LOG_ERROR("Copy IP", nRetVal); + m_nPort = nPort; + m_nMaxPacketSize = nMaxPacketSize; + m_bInitialized = TRUE; + + return XN_STATUS_OK; +} + +void SyncSocketConnection::Shutdown() +{ + Disconnect(); + m_bInitialized = FALSE; +} + + +XnBool SyncSocketConnection::IsInitialized() const +{ + return m_bInitialized; +} + +XnStatus SyncSocketConnection::Connect() +{ + if (IsConnected()) + { + return XN_STATUS_OK; + } + + XnStatus nRetVal = xnOSCreateSocket(XN_OS_TCP_SOCKET, m_strIP, m_nPort, &m_hSocket); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_SYNC_SOCKET, "Failed to create socket %s:%u: %s", m_strIP, m_nPort, xnGetStatusString(nRetVal)); + m_hSocket = NULL; + return nRetVal; + } + nRetVal = xnOSConnectSocket(m_hSocket, CONNECT_TIMEOUT); + if (nRetVal != XN_STATUS_OK) + { + xnLogError(XN_MASK_SYNC_SOCKET, "Failed to connect socket %s:%u: %s", m_strIP, m_nPort, xnGetStatusString(nRetVal)); + xnOSCloseSocket(m_hSocket); + m_hSocket = NULL; + return nRetVal; + } + + return XN_STATUS_OK; +} + +void SyncSocketConnection::Disconnect() +{ + if (m_hSocket != NULL) + { + xnOSCloseSocket(m_hSocket); + m_hSocket = NULL; + } +} + +XnBool SyncSocketConnection::IsConnected() const +{ + return (m_hSocket != NULL); +} + +const XnChar* SyncSocketConnection::GetIP() const +{ + return m_strIP; +} + +XnUInt16 SyncSocketConnection::GetPort() const +{ + return m_nPort; +} + +XnStatus SyncSocketConnection::Receive(void* pData, XnUInt32& nSize) +{ + XnStatus nRetVal = XN_STATUS_OK; + LinkPacketHeader* pLinkPacketHeader = reinterpret_cast(pData); + XnUInt32 nMaxSize = nSize; + XnUInt32 nTotalBytesReceived = sizeof(LinkPacketHeader); + + nRetVal = xnOSReceiveNetworkBuffer(m_hSocket, (XnChar*)pData, &nTotalBytesReceived, RECEIVE_TIMEOUT); + if (nRetVal == XN_STATUS_OS_NETWORK_TIMEOUT) // Do not use XN_IS_STATUS_OK_LOG_ERROR for timeout + { + return nRetVal; + } + //XN_IS_STATUS_OK_LOG_ERROR("Receive network buffer", nRetVal); + XN_IS_STATUS_OK(nRetVal); + + if (nTotalBytesReceived < sizeof(LinkPacketHeader)) + { + xnLogError(XN_MASK_SYNC_SOCKET, "Partial link packet header received :("); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + XnUInt32 nBytesToRead = pLinkPacketHeader->GetSize(); + if (nBytesToRead > nMaxSize) + { + xnLogError(XN_MASK_SYNC_SOCKET, "Specified buffer of size %u is not large enough to hold received packet of size %u", nMaxSize, nBytesToRead); + XN_ASSERT(FALSE); + return XN_STATUS_INTERNAL_BUFFER_TOO_SMALL; + } + + XnUInt32 nIterationBytesReceived = 0; + while (nTotalBytesReceived < nBytesToRead) + { + nIterationBytesReceived = (nBytesToRead - nTotalBytesReceived); + nRetVal = xnOSReceiveNetworkBuffer(m_hSocket, + ((XnChar*)pData) + nTotalBytesReceived, + &nIterationBytesReceived, + RECEIVE_TIMEOUT); + XN_IS_STATUS_OK_LOG_ERROR("Receive network buffer", nRetVal); + nTotalBytesReceived += nIterationBytesReceived; + } + + nSize = nTotalBytesReceived; + + return XN_STATUS_OK; +} + +XnStatus SyncSocketConnection::Send(const void* pData, XnUInt32 nSize) +{ + if (nSize > 0) + { + XnStatus nRetVal = xnOSSendNetworkBuffer(m_hSocket, reinterpret_cast(pData), nSize); + XN_IS_STATUS_OK(nRetVal); + } + return XN_STATUS_OK; +} + +XnUInt16 SyncSocketConnection::GetMaxPacketSize() const +{ + return m_nMaxPacketSize; +} + +} diff --git a/Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.h b/Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.h new file mode 100644 index 0000000..3ec9428 --- /dev/null +++ b/Source/Drivers/PSLink/LinkProtoLib/XnSyncSocketConnection.h @@ -0,0 +1,50 @@ +#ifndef __SYNCSOCKETCONNECTION_H__ +#define __SYNCSOCKETCONNECTION_H__ + +#include "ISyncIOConnection.h" +#include "XnLinkProtoLibDefs.h" + +struct xnOSSocket; +typedef struct xnOSSocket* XN_SOCKET_HANDLE; + +namespace xn +{ + +class SyncSocketConnection : virtual public ISyncIOConnection +{ +public: + SyncSocketConnection(); + SyncSocketConnection(const SyncSocketConnection& other); + virtual ~SyncSocketConnection(); + SyncSocketConnection& operator=(const SyncSocketConnection& other); + +// Operations + virtual XnStatus Init(const XnChar* strIP, XnUInt16 nPort, XnUInt16 nMaxPacketSize); + virtual void Shutdown(); + XnBool IsInitialized() const; + + virtual XnBool IsConnected() const; + const XnChar* GetIP() const; + XnUInt16 GetPort() const; + +// ISyncIOConnection implementation + virtual XnStatus Connect(); + virtual void Disconnect(); + virtual XnStatus Receive(void* pData, XnUInt32& nSize); + virtual XnStatus Send(const void* pData, XnUInt32 nSize); + virtual XnUInt16 GetMaxPacketSize() const; + + static XnUInt32 CONNECT_TIMEOUT; + static XnUInt32 RECEIVE_TIMEOUT; + +protected: + XnBool m_bInitialized; + XnConnectionString m_strIP; + XnUInt16 m_nPort; + XnUInt16 m_nMaxPacketSize; + XN_SOCKET_HANDLE m_hSocket; +}; + +} + +#endif // __SYNCSOCKETCONNECTION_H__ \ No newline at end of file diff --git a/Source/Drivers/PSLink/Makefile b/Source/Drivers/PSLink/Makefile new file mode 100644 index 0000000..7a72def --- /dev/null +++ b/Source/Drivers/PSLink/Makefile @@ -0,0 +1,39 @@ +include ../../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../../Bin + +INC_DIRS = \ + ../../../Include \ + ../../../ThirdParty/PSCommon/XnLib/Include \ + . \ + Protocols/XnLinkProto \ + LinkProtoLib \ + +SRC_FILES = \ + *.cpp \ + DriverImpl/*.cpp \ + LinkProtoLib/*.cpp + +LIB_DIRS = ../../../ThirdParty/PSCommon/XnLib/Bin/$(PLATFORM)-$(CFG) +LIB_DIRS += $(BIN_DIR)/$(PLATFORM)-$(CFG) +USED_LIBS = XnLib dl pthread + +ifeq ("$(OSTYPE)","Darwin") + INC_DIRS += /opt/local/include + LIB_DIRS += /opt/local/lib + LDFLAGS += -framework CoreFoundation -framework IOKit +endif + +ifneq ("$(OSTYPE)","Darwin") + USED_LIBS += rt usb-1.0 udev +else + USED_LIBS += usb-1.0.0 +endif + +CFLAGS += -Wall + +LIB_NAME = PSLink + +OUT_DIR := $(OUT_DIR)/OpenNI2/Drivers + +include ../../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Source/Drivers/PSLink/PS1200Device.cpp b/Source/Drivers/PSLink/PS1200Device.cpp new file mode 100644 index 0000000..caff56e --- /dev/null +++ b/Source/Drivers/PSLink/PS1200Device.cpp @@ -0,0 +1,222 @@ +#include "PS1200Device.h" +#include "XnClientUSBConnectionFactory.h" +#include "XnSocketConnectionFactory.h" +#include +#include + +#define XN_MASK_PS1200_DEVICE "PS1200Device" + +namespace xn +{ + +const XnUInt32 PS1200Device::WAIT_FOR_FREE_BUFFER_TIMEOUT_MS = XN_WAIT_INFINITE; +const XnUInt32 PS1200Device::NUM_OUTPUT_CONNECTIONS = 0; +const XnUInt32 PS1200Device::NUM_INPUT_CONNECTIONS = 3; +const XnUInt32 PS1200Device::PRE_CONTROL_RECEIVE_SLEEP = 0; + +PS1200Device::PS1200Device() +{ + m_hInputInterruptCallback = NULL; + m_bInitialized = FALSE; +} + +PS1200Device::~PS1200Device() +{ + Shutdown(); +} + +XnStatus PS1200Device::Init(const XnChar* strConnString, XnTransportType transportType) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (transportType != XN_TRANSPORT_TYPE_USB) + { + xnLogError(XN_MASK_LINK, "Transport type not supported: %d", transportType); + XN_ASSERT(FALSE); + return XN_STATUS_BAD_PARAM; + } + + nRetVal = PrimeClient::Init(strConnString, XN_TRANSPORT_TYPE_USB); + XN_IS_STATUS_OK_LOG_ERROR("Init EE Device", nRetVal); + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + // On all platforms other than Windows, prefer BULK + nRetVal = SetUsbAltInterface(0); + XN_IS_STATUS_OK_LOG_ERROR("Switch to ISO", nRetVal); +#elif (XN_PLATFORM == XN_PLATFORM_LINUX_X86 || XN_PLATFORM == XN_PLATFORM_LINUX_ARM || XN_PLATFORM == XN_PLATFORM_MACOSX || XN_PLATFORM == XN_PLATFORM_ANDROID_ARM) + // On all platforms other than Windows, prefer BULK + nRetVal = SetUsbAltInterface(1); + XN_IS_STATUS_OK_LOG_ERROR("Switch to BULK", nRetVal); +#else + #error "Unsupported platform" +#endif + + m_bInitialized = TRUE; + return XN_STATUS_OK; +} + +void PS1200Device::Shutdown() +{ + PrimeClient::Shutdown(); + m_bInitialized = FALSE; +} + +XnBool PS1200Device::IsInitialized() const +{ + return m_bInitialized; +} + +IConnectionFactory* PS1200Device::CreateConnectionFactory(XnTransportType transportType) +{ + if (transportType != XN_TRANSPORT_TYPE_USB) + { + XN_ASSERT(FALSE); + return NULL; + } + + return XN_NEW(ClientUSBConnectionFactory, + NUM_INPUT_CONNECTIONS, + NUM_OUTPUT_CONNECTIONS, + PRE_CONTROL_RECEIVE_SLEEP); +} + +ClientUSBConnectionFactory* PS1200Device::GetConnectionFactory() +{ + return (ClientUSBConnectionFactory*)m_pConnectionFactory; +} + +const ClientUSBConnectionFactory* PS1200Device::GetConnectionFactory() const +{ + return (const ClientUSBConnectionFactory*)m_pConnectionFactory; +} + +XnStatus PS1200Device::SetUsbAltInterface(XnUInt8 altInterface) +{ + return GetConnectionFactory()->SetUsbAltInterface(altInterface); +} + +XnStatus PS1200Device::GetUsbAltInterface(XnUInt8& altInterface) const +{ + return GetConnectionFactory()->GetUsbAltInterface(&altInterface); +} + +class UsbEndpointTester : public IDataDestination +{ +public: + void Reset() + { + m_nCounter = 0; + m_nTotalBytes = 0; + m_nLostPackets = 0; + } + + virtual XnStatus IncomingData(const void* pData, XnUInt32 nSize) + { + m_nTotalBytes += nSize; + + const XnUInt8* pCurData = (const XnUInt8*)pData; + const XnUInt8* pEndData = pCurData + nSize; + + while (pCurData < pEndData) + { + // first word is a counter + const XnUInt32* pDWords = (const XnUInt32*)pCurData; + XnUInt32 nPacketSize = pDWords[0]; + XnUInt32 nCounter = pDWords[1]; + + XnUInt32 nLostPackets = (nCounter - m_nCounter - 1); + + m_nLostPackets += nLostPackets; + m_nCounter = nCounter; + pCurData += nPacketSize; + } + + return XN_STATUS_OK; + } + + virtual void HandleDisconnection() + { + xnLogWarning(XN_MASK_PS1200_DEVICE, "Endpoint disconnected during USB test!"); + } + + XnUInt32 m_nEP; + XnUInt32 m_nTotalBytes; + XnUInt32 m_nLostPackets; + +private: + int m_nCounter; +}; + +XnStatus PS1200Device::UsbTest(XnUInt32 nSeconds, XnUInt32& endpointsCount, XnUsbTestEndpointResult* endpoints) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xn::ClientUSBConnectionFactory* pConnFactory = GetConnectionFactory(); + + if (m_linkInputStreamsMgr.HasStreams()) + { + xnLogWarning(XN_MASK_PS1200_DEVICE, "Can't start USB test when other streams exists!"); + return XN_STATUS_ERROR; + } + + XnUInt16 nNumEndpoints = pConnFactory->GetNumInputDataConnections(); + if (nNumEndpoints > endpointsCount) + { + xnLogWarning(XN_MASK_PS1200_DEVICE, "Endpoints array is too small"); + return XN_STATUS_BAD_PARAM; + } + + xn::IAsyncInputConnection* aEndpoints[20]; + UsbEndpointTester aTesters[20]; + + for (int i = 0; i < nNumEndpoints; ++i) + { + nRetVal = pConnFactory->CreateInputDataConnection((XnUInt16)i, aEndpoints[i]); + if (nRetVal != XN_STATUS_OK) + { + for (int j = 0; j < i; ++j) + { + XN_DELETE(aEndpoints[j]); + } + return nRetVal; + } + + aTesters[i].Reset(); + aTesters[i].m_nEP = i; + aEndpoints[i]->SetDataDestination(&aTesters[i]); + aEndpoints[i]->Connect(); + } + + nRetVal = m_linkControlEndpoint.StartUsbTest(); + if (nRetVal != XN_STATUS_OK) + { + for (int i = 0; i < nNumEndpoints; ++i) + { + XN_DELETE(aEndpoints[i]); + } + return nRetVal; + } + + // let the test run + xnOSSleep(nSeconds*1000); + + nRetVal = m_linkControlEndpoint.StopUsbTest(); + if (nRetVal != XN_STATUS_OK) + { + xnLogWarning(XN_MASK_PS1200_DEVICE, "Failed to stop USB test!"); + XN_ASSERT(FALSE); + } + + for (int i = 0; i < nNumEndpoints; ++i) + { + XN_DELETE(aEndpoints[i]); + + endpoints[i].averageBytesPerSecond = aTesters[i].m_nTotalBytes / (XnDouble)nSeconds; + endpoints[i].lostPackets = aTesters[i].m_nLostPackets; + } + endpointsCount = nNumEndpoints; + + return (XN_STATUS_OK); +} + +} diff --git a/Source/Drivers/PSLink/PS1200Device.h b/Source/Drivers/PSLink/PS1200Device.h new file mode 100644 index 0000000..4886d54 --- /dev/null +++ b/Source/Drivers/PSLink/PS1200Device.h @@ -0,0 +1,53 @@ +#ifndef __PS1200DEVICE_H__ +#define __PS1200DEVICE_H__ + +#include "PrimeClient.h" +#include + +namespace xn +{ + +class ClientUSBConnectionFactory; + +class PS1200Device : public PrimeClient +{ +public: + PS1200Device(); + virtual ~PS1200Device(); + + virtual XnStatus Init(const XnChar* strConnString, XnTransportType transportType); + virtual void Shutdown(); + virtual XnBool IsInitialized() const; + + XnStatus SetUsbAltInterface(XnUInt8 altInterface); + XnStatus GetUsbAltInterface(XnUInt8& altInterface) const; + + virtual XnStatus UsbTest(XnUInt32 nSeconds, XnUInt32& endpointsCount, XnUsbTestEndpointResult* endpoints); + +protected: + const ClientUSBConnectionFactory* GetConnectionFactory() const; + ClientUSBConnectionFactory* GetConnectionFactory(); + IConnectionFactory* CreateConnectionFactory(XnTransportType transportType); + +private: + static const XnUInt32 WAIT_FOR_FREE_BUFFER_TIMEOUT_MS; + static const XnUInt32 USB_READ_BUFFERS; + static const XnUInt32 USB_READ_BUFFER_PACKETS; + static const XnUInt32 USB_READ_TIMEOUT; + static const XnUSBEndPointType ENDPOINTS_TYPE; + static const XnUInt32 NUM_OUTPUT_CONNECTIONS; + static const XnUInt32 NUM_INPUT_CONNECTIONS; + static const XnUInt32 PRE_CONTROL_RECEIVE_SLEEP; + + //Data members + XnBool m_bInitialized; + + XnCallbackHandle m_hInputInterruptCallback; + + //Hide assignment operator + PS1200Device& operator=(const PS1200Device&); +}; + +} + +#endif // __PS1200DEVICE_H__ diff --git a/Source/Drivers/PSLink/PSLink.vcxproj b/Source/Drivers/PSLink/PSLink.vcxproj new file mode 100644 index 0000000..8915163 --- /dev/null +++ b/Source/Drivers/PSLink/PSLink.vcxproj @@ -0,0 +1,302 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {5B74F010-8B79-46B5-B906-C2B56CDB3386} + BestHostEver + Win32Proj + + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + false + + + + Disabled + .;Protocols\XnLinkProto;LinkProtoLib;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;PRIMECLIENT_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + EditAndContinue + 4250;4127;%(DisableSpecificWarnings) + true + true + + + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration) + true + Windows + MachineX86 + true + + + + + X64 + + + /MP %(AdditionalOptions) + Disabled + .;Protocols\XnLinkProto;LinkProtoLib;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;PRIMECLIENT_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration) + true + Windows + MachineX64 + true + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + .;Protocols\XnLinkProto;LinkProtoLib;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;PRIMECLIENT_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration) + true + Windows + true + true + MachineX86 + true + + + + + X64 + + + /MP %(AdditionalOptions) + MaxSpeed + true + .;Protocols\XnLinkProto;LinkProtoLib;..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;PRIMECLIENT_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration) + true + Windows + true + true + MachineX64 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Drivers/PSLink/PSLink.vcxproj.filters b/Source/Drivers/PSLink/PSLink.vcxproj.filters new file mode 100644 index 0000000..bcf9564 --- /dev/null +++ b/Source/Drivers/PSLink/PSLink.vcxproj.filters @@ -0,0 +1,331 @@ + + + + + {3450fb6e-076f-4eda-b236-03d7c9f3fc75} + + + {4a61ff45-f315-4a09-b7b2-c23af7a34d4a} + + + {9ad7f4c4-30f7-4229-a39e-869596ebeb88} + + + + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + + + Protocol + + + Protocol + + + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + DriverImpl + + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + LinkProtoLib + + + + + \ No newline at end of file diff --git a/Source/Drivers/PSLink/PSLinkConsole/Makefile b/Source/Drivers/PSLink/PSLinkConsole/Makefile new file mode 100644 index 0000000..0f0ff97 --- /dev/null +++ b/Source/Drivers/PSLink/PSLinkConsole/Makefile @@ -0,0 +1,36 @@ +include ../../../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../../../Bin + +INC_DIRS = \ + ../../../../Include \ + ../../../../ThirdParty/PSCommon/XnLib/Include \ + ../ \ + ../Protocols/XnLinkProto \ + ../LinkProtoLib \ + +SRC_FILES = \ + *.cpp \ + ../LinkProtoLib/*.cpp \ + ../*.cpp \ + +LIB_DIRS = ../../../../ThirdParty/PSCommon/XnLib/Bin/$(PLATFORM)-$(CFG) +USED_LIBS = OpenNI2 XnLib dl pthread + +ifeq ("$(OSTYPE)","Darwin") + INC_DIRS += /opt/local/include + LIB_DIRS += /opt/local/lib + LDFLAGS += -framework CoreFoundation -framework IOKit +endif + +ifneq ("$(OSTYPE)","Darwin") + USED_LIBS += rt usb-1.0 udev +else + USED_LIBS += usb-1.0.0 +endif + +CFLAGS += -Wall + +EXE_NAME = PSLinkConsole + +include ../../../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.cpp b/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.cpp new file mode 100644 index 0000000..922cfe6 --- /dev/null +++ b/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.cpp @@ -0,0 +1,1672 @@ +// PSLinkConsole.cpp : Defines the entry point for the console application. +// + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace openni; + +//--------------------------------------------------------------------------- +// Macros +//--------------------------------------------------------------------------- +#define CHECK_RC(what, nRetVal) \ + if (nRetVal != XN_STATUS_OK) \ + { \ + printf("Failed to %s: %s\n", what, OpenNI::getExtendedError()); \ + XN_ASSERT(FALSE); \ + return nRetVal; \ + } + +#define CHECK_RC_NO_RET(what, nRetVal) \ + if (nRetVal != XN_STATUS_OK) \ + { \ + printf("Failed to %s: %s\n", what, OpenNI::getExtendedError()); \ + XN_ASSERT(FALSE); \ + } + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +#define WAIT_FOR_DEVICE_TIMEOUT 10000 +#define WAIT_FOR_DEVICE_CHECK_INTERVAL_MS 5000 +#define XN_MASK_PRIME_CONSOLE "PSLinkConsole" +#define MAX_STREAMS_COUNT 10 + +//--------------------------------------------------------------------------- +// Types +//--------------------------------------------------------------------------- +typedef int (*CommandHandler)(int argc, const char* argv[]); + +typedef struct +{ + const XnChar* name; + CommandHandler handler; +} Command; + +typedef struct +{ + XnFwStreamType type; + const char* name; +} FwStreamName; + +typedef struct +{ + XnUsbInterfaceType type; + const char* name; +} UsbInterfaceName; + +//--------------------------------------------------------------------------- +// Globals +//--------------------------------------------------------------------------- +static Device g_device; +static xnl::Array g_streams; +static xnl::List g_commandsList; +static XnStringsHashT g_commands; +static XnBool g_continue = TRUE; + +static FwStreamName g_fwStreamNames[] = { + { XN_FW_STREAM_TYPE_COLOR, "Color" }, + { XN_FW_STREAM_TYPE_IR, "IR" }, + { XN_FW_STREAM_TYPE_SHIFTS, "Depth" }, + { XN_FW_STREAM_TYPE_AUDIO, "Audio" }, + { XN_FW_STREAM_TYPE_DY, "DY" }, + { XN_FW_STREAM_TYPE_LOG, "Log" }, +}; + +static UsbInterfaceName g_usbInterfaceNames[] = { + { PS_USB_INTERFACE_DONT_CARE, "ANY" }, + { PS_USB_INTERFACE_ISO_ENDPOINTS, "ISO" }, + { PS_USB_INTERFACE_BULK_ENDPOINTS, "BULK" }, +}; + +//--------------------------------------------------------------------------- +// Forward Declarations +//--------------------------------------------------------------------------- +void ExecuteCommandsFromStream(FILE* inputStream, XnBool bPrompt); + +//--------------------------------------------------------------------------- +// Utilities +//--------------------------------------------------------------------------- + +//pnTokens is max args on input, actual number of args on output +void SplitStr(XnChar* str, const XnChar* strTokens[], int* pnTokens) +{ + XnUInt32 nMaxTokens = *pnTokens; + XnUInt32 nToken = 0; + + XnBool bFirstAfterDelim = TRUE; + + for (XnChar* p = str; *p != '\0' && nToken < nMaxTokens ; ++p) + { + if (*p == ' ' || *p == '\n') + { + *p = '\0'; + bFirstAfterDelim = TRUE; + } + else if (bFirstAfterDelim) + { + strTokens[nToken++] = p; + bFirstAfterDelim = FALSE; + } + } + + *pnTokens = nToken; +} + +//MyAtoi() accepts hex (with 0x) or decimal values from string +XnUInt32 MyAtoi(const XnChar* str) +{ + XnUInt32 nValue = 0; + sscanf(str, "%i", &nValue); + return nValue; +} + +XnChar* ToLower(XnChar* str) +{ + for (XnChar* p = str; *p != '\0'; ++p) + { + *p = (XnChar)tolower(*p); + } + return str; +} + +const char* fwStreamTypeToName(XnFwStreamType type) +{ + for (size_t i = 0; i < sizeof(g_fwStreamNames) / sizeof(g_fwStreamNames[0]); ++i) + { + if (g_fwStreamNames[i].type == type) + { + return g_fwStreamNames[i].name; + } + } + + XN_ASSERT(FALSE); + return NULL; +} + +XnFwStreamType fwStreamNameToType(const char* name) +{ + for (size_t i = 0; i < sizeof(g_fwStreamNames) / sizeof(g_fwStreamNames[0]); ++i) + { + if (xnOSStrCaseCmp(g_fwStreamNames[i].name, name) == 0) + { + return g_fwStreamNames[i].type; + } + } + + return (XnFwStreamType)-1; +} + +const XnChar* fwPixelFormatToName(XnFwPixelFormat pixelFormat) +{ + switch (pixelFormat) + { + case XN_FW_PIXEL_FORMAT_SHIFTS_9_3: + return "Shifts9.3"; + case XN_FW_PIXEL_FORMAT_GRAYSCALE16: + return "Grayscale16"; + case XN_FW_PIXEL_FORMAT_YUV422: + return "YUV422"; + case XN_FW_PIXEL_FORMAT_BAYER8: + return "BAYER8"; + default: + XN_ASSERT(FALSE); + return "UNKNOWN"; + } +} + +XnFwPixelFormat fwPixelFormatNameToType(const XnChar* name) +{ + if (xnOSStrCmp(name, "Shifts9.3") == 0) + return XN_FW_PIXEL_FORMAT_SHIFTS_9_3; + else if (xnOSStrCmp(name, "Grayscale16") == 0) + return XN_FW_PIXEL_FORMAT_GRAYSCALE16; + else if (xnOSStrCmp(name, "YUV422") == 0) + return XN_FW_PIXEL_FORMAT_YUV422; + else if (xnOSStrCmp(name, "BAYER8") == 0) + return XN_FW_PIXEL_FORMAT_BAYER8; + else + { + XN_ASSERT(FALSE); + return (XnFwPixelFormat)(-1); + } +} + +const XnChar* fwCompressionTypeToName(XnFwCompressionType compression) +{ + switch (compression) + { + case XN_FW_COMPRESSION_NONE: + return "None"; + case XN_FW_COMPRESSION_8Z: + return "8z"; + case XN_FW_COMPRESSION_16Z: + return "16z"; + case XN_FW_COMPRESSION_24Z: + return "24z"; + case XN_FW_COMPRESSION_6_BIT_PACKED: + return "6bit"; + case XN_FW_COMPRESSION_10_BIT_PACKED: + return "10bit"; + case XN_FW_COMPRESSION_11_BIT_PACKED: + return "11bit"; + case XN_FW_COMPRESSION_12_BIT_PACKED: + return "12bit"; + default: + XN_ASSERT(FALSE); + return "UNKNOWN"; + } +} + +XnFwCompressionType fwCompressionNameToType(const XnChar* name) +{ + if (xnOSStrCmp(name, "None") == 0) + return XN_FW_COMPRESSION_NONE; + else if (xnOSStrCmp(name, "8z") == 0) + return XN_FW_COMPRESSION_8Z; + else if (xnOSStrCmp(name, "16z") == 0) + return XN_FW_COMPRESSION_16Z; + else if (xnOSStrCmp(name, "24z") == 0) + return XN_FW_COMPRESSION_24Z; + else if (xnOSStrCmp(name, "6bit") == 0) + return XN_FW_COMPRESSION_6_BIT_PACKED; + else if (xnOSStrCmp(name, "10bit") == 0) + return XN_FW_COMPRESSION_10_BIT_PACKED; + else if (xnOSStrCmp(name, "11bit") == 0) + return XN_FW_COMPRESSION_11_BIT_PACKED; + else if (xnOSStrCmp(name, "12bit") == 0) + return XN_FW_COMPRESSION_12_BIT_PACKED; + else + { + XN_ASSERT(FALSE); + return (XnFwCompressionType)-1; + } +} + +const char* fwVideoModeToString(XnFwStreamVideoMode videoMode) +{ + static char buffer[256]; + XnUInt32 charsWritten = 0; + xnOSStrFormat(buffer, sizeof(buffer), &charsWritten, "%ux%u@%u (%s, %s)", + videoMode.m_nXRes, videoMode.m_nYRes, videoMode.m_nFPS, + fwPixelFormatToName(videoMode.m_nPixelFormat), + fwCompressionTypeToName(videoMode.m_nCompression)); + return buffer; +} + +//--------------------------------------------------------------------------- +// Framework +//--------------------------------------------------------------------------- +void RunCommand(XnChar* strCmdLine) +{ + enum {CMD_MAX_ARGS = 256}; + const char* argv[CMD_MAX_ARGS] = {NULL}; + int argc = CMD_MAX_ARGS; + + SplitStr(strCmdLine, argv, &argc); + + if (argc == 0) + { + //Ignore empty lines + return; + } + + char commandName[XN_FILE_MAX_PATH]; + xnOSStrCopy(commandName, argv[0], sizeof(commandName)); + + if (commandName[0] == '#') + { + //This is a comment - ignore it + return; + } + + ToLower(commandName); + + Command command; + if (XN_STATUS_OK != g_commands.Get(commandName, command)) + { + printf("Invalid command '%s'. Try 'Help' for all available commands.\n\n", argv[0]); + return; + } + + command.handler(argc, argv); +} + +void ExecuteCommandsFromStream(FILE* pStream, XnBool bPrompt) +{ + XnChar strCmdLine[1024]; + XnBool bEOF = FALSE; + + while (g_continue && !bEOF) + { + if (bPrompt) + { + printf("PSLinkConsole>"); + } + + // read command from stream + if (fgets(strCmdLine, sizeof(strCmdLine), pStream) != 0) + { + RunCommand(strCmdLine); + } + else + { + if (ferror(pStream)) + { + printf("Error reading from input stream: %d\n", errno); + } + //if fgets returns 0 and this is not an error then it's eof + bEOF = TRUE; + } + } // commands loop +} + +void RegisterCommand(const XnChar* cmd, CommandHandler handler) +{ + Command command; + command.name = cmd; + command.handler = handler; + + XnChar commandName[XN_FILE_MAX_PATH]; + xnOSStrCopy(commandName, cmd, sizeof(commandName)); + ToLower(commandName); + g_commands.Set(commandName, command); + + g_commandsList.AddLast(cmd); +} + +int RunScript(const XnChar* strFileName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + XnBool bExists = FALSE; + nRetVal = xnOSDoesFileExist(strFileName, &bExists); + if (nRetVal != XN_STATUS_OK) + { + printf("Failed checking for file existence: %s\n", OpenNI::getExtendedError()); + return -1; + } + + if (!bExists) + { + printf("Script file '%s' not found\n", strFileName); + return -2; + } + + printf("Running script file '%s'\n", strFileName); + FILE* pScriptFile = fopen(strFileName, "r"); + if (pScriptFile == NULL) + { + printf("Failed to open script file '%s'\n", strFileName); + return -3; + } + + ExecuteCommandsFromStream(pScriptFile, FALSE); + fclose(pScriptFile); + + return nRetVal; +} + +//--------------------------------------------------------------------------- +// Commands +//--------------------------------------------------------------------------- +int Help(int /*argc*/, const char* /*argv*/[]) +{ + printf("Supported commands:\n"); + for (xnl::List::Iterator it = g_commandsList.Begin(); it != g_commandsList.End(); ++it) + { + printf("\t%s\n", *it); + } + + return 0; +} + +int BeginUpload(int /*argc*/, const char* /*argv*/[]) +{ + Status nRetVal = g_device.invoke(PS_COMMAND_BEGIN_FIRMWARE_UPDATE, NULL, 0); + if (nRetVal == STATUS_OK) + { + printf("Begin upload successful\n\n"); + return 0; + } + else + { + printf("Begin upload failed: %s\n\n", OpenNI::getExtendedError()); + return nRetVal; + } +} + +int EndUpload(int /*argc*/, const char* /*argv*/[]) +{ + Status nRetVal = g_device.invoke(PS_COMMAND_END_FIRMWARE_UPDATE, NULL, 0); + if (nRetVal == STATUS_OK) + { + printf("End upload successful\n\n"); + return 0; + } + else + { + printf("End upload failed: %s\n\n", OpenNI::getExtendedError()); + return nRetVal; + } +} + +int Upload(int argc, const char* argv[]) +{ + if (argc < 2 || (argc >= 3 && xnOSStrCaseCmp(argv[2], "factory") != 0)) + { + printf("Usage: %s [factory]\n", argv[0]); + printf("Note: filename can not contain any spaces.\n\n"); + return -1; + } + + XnCommandUploadFile uploadCommand; + uploadCommand.uploadToFactory = (argc > 2); + uploadCommand.filePath = argv[1]; + + printf("Uploading file '%s'...", uploadCommand.filePath); + + Status nRetVal = g_device.invoke(PS_COMMAND_UPLOAD_FILE, uploadCommand); + printf("\n"); + if (nRetVal == STATUS_OK) + { + printf("File uploaded successfully\n\n"); + return 0; + } + else + { + printf("Failed to upload file '%s': %s\n\n", uploadCommand.filePath, OpenNI::getExtendedError()); + return nRetVal; + } +} + +int Dir(int /*argc*/, const char* /*argv*/[]) +{ + XnFwFileEntry files[50]; + XnCommandGetFileList args; + args.count = sizeof(files) / sizeof(files[0]); + args.files = files; + + Status nRetVal = g_device.invoke(PS_COMMAND_GET_FILE_LIST, args); + if (nRetVal != STATUS_OK) + { + printf("Failed to get file list: %s\n\n", OpenNI::getExtendedError()); + return nRetVal; + } + else + { + // print list + printf("\n"); + printf("%-4s %-32s %-8s %-10s %-6s %-6s %-15s\n", "ZONE", "NAME", "VERSION", "ADDRESS", "SIZE", "CRC", "FLAGS"); + printf("%-4s %-32s %-8s %-10s %-6s %-6s %-15s\n", "====", "====", "=======", "=======", "====", "===", "====="); + + for (XnUInt32 i = 0; i < args.count; ++i) + { + XnFwFileEntry& file = files[i]; + printf("%-4u %-32s %01u.%01u.%01u.%02u 0x%08x %6u 0x%04x ", + file.zone, file.name, + file.version.major, file.version.minor, file.version.maintenance, file.version.build, + file.address, file.size, file.crc); + + // flags + if ((file.flags & XN_FILE_FLAG_BAD_CRC) != 0) + { + printf("CORRUPT"); + } + + printf("\n"); + } + printf("\n"); + } + + return 0; +} + +int Download(int argc, const char* argv[]) +{ + if (argc < 4) + { + printf("Usage: %s \n", argv[0]); + printf("Note: filename can not contain any spaces.\n\n"); + return -1; + } + + XnCommandDownloadFile args; + args.zone = (uint16_t)MyAtoi(argv[1]); + args.firmwareFileName = argv[2]; + args.targetPath = argv[3]; + + Status nRetVal = g_device.invoke(PS_COMMAND_DOWNLOAD_FILE, args); + if (nRetVal != STATUS_OK) + { + printf("Failed to download file '%s' from zone %u: %s\n\n", args.firmwareFileName, args.zone, OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int PrintFirmwareVersion(int /*argc*/, const char* /*argv*/[]) +{ + char strVersion[200]; + int size = sizeof(strVersion); + Status nRetVal = g_device.getProperty(DEVICE_PROPERTY_FIRMWARE_VERSION, strVersion, &size); + if (nRetVal != STATUS_OK) + { + printf("Failed to get firmware version!"); + return -1; + } + + printf("FW version from device: %s\n\n", strVersion); + return 0; +} + +int DumpStream(int argc, const char* argv[]) +{ + if (argc < 3) + { + printf("Usage: %s \n\n", argv[0]); + return -1; + } + + XnUInt16 nStreamID = (XnUInt16)MyAtoi(argv[1]); + XnBool bDumpOn = (xnOSStrCaseCmp(argv[2], "on") == 0); + if (STATUS_OK != g_device.setProperty(PS_PROPERTY_DUMP_DATA, bDumpOn)) + { + printf("Failed to toggle dump for stream\n"); + return -2; + } + + if (bDumpOn) + { + printf("Dumping stream %u to directory 'Log'\n\n", nStreamID); + } + else + { + printf("Stream %u dump is now off\n\n", nStreamID); + } + + return 0; +} + +int DumpEP(int argc, const char* argv[]) +{ + if (argc < 3) + { + printf("Usage: %s \n\n", argv[0]); + return -1; + } + + XnCommandDumpEndpoint args; + + args.endpoint = (uint8_t)MyAtoi(argv[1]); + args.enabled = xnOSStrCaseCmp(argv[2], "on") == 0; + + Status nRetVal = g_device.invoke(PS_COMMAND_DUMP_ENDPOINT, args); + if (nRetVal != STATUS_OK) + { + printf("Failed to set endpoint dump: %s\n", OpenNI::getExtendedError()); + return 1; + } + + if (args.enabled) + { + printf("Dumping endpoint %u to directory 'Log'\n\n", args.endpoint); + } + else + { + printf("Endpoint %u dump is now off\n\n", args.endpoint); + } + + return 0; +} + +int EnumerateStreams(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + printf("Enumerate available stream of a certain type, where stream type is one of the following:\n"); + printf("\tDepth, IR, Log, Image, User, Hands, Gestures, DY\n\n"); + return -1; + } + + XnFwStreamType type = (XnFwStreamType)-1; + if (xnOSStrCaseCmp(argv[1], "all") != 0) + { + type = fwStreamNameToType(argv[1]); + if (type == (XnFwStreamType)-1) + { + printf("Bad stream type '%s'\n\n", argv[1]); + return -2; + } + } + + XnFwStreamInfo streams[20]; + XnCommandGetFwStreamList args; + args.count = sizeof(streams)/sizeof(streams[0]); + args.streams = streams; + Status nRetVal = g_device.invoke(LINK_COMMAND_GET_FW_STREAM_LIST, args); + if (nRetVal == STATUS_OK) + { + int index = 0; + for (uint32_t i = 0; i < args.count; ++i) + { + if (type == (XnFwStreamType)-1 || type == streams[i].type) + { + printf("\t[%u] stream type='%s', creationInfo='%s'\n", + index++, + fwStreamTypeToName(streams[i].type), + streams[i].creationInfo); + } + } + } + else + { + printf("Failed to enumerate streams: %s\n", OpenNI::getExtendedError()); + } + printf("\n"); + + return nRetVal; +} + +int CreateStream(int argc, const char* argv[]) +{ + if (argc < 3) + { + printf("Usage: %s \n", argv[0]); + printf("Creates a stream, where node type and creation info are values returned from the Enum command.\n\n"); + return -1; + } + + XnFwStreamType streamType = fwStreamNameToType(argv[1]); + if (streamType == (XnFwStreamType)-1) + { + printf("Bad stream type '%s'.\n\n", argv[1]); + return -2; + } + + XnCommandCreateStream args; + args.type = streamType; + args.creationInfo = argv[2]; + + Status nRetVal = g_device.invoke(LINK_COMMAND_CREATE_FW_STREAM, args); + if (nRetVal == XN_STATUS_OK) + { + printf("Successfully created stream of type %s with ID %u.\n\n", argv[1], args.id); + } + else + { + printf("Failed to create stream: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int DestroyStream(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + printf("Destroys a stream, where stream ID is an ID returned from the Create command.\n"); + return -1; + } + + XnCommandDestroyStream args; + args.id = MyAtoi(argv[1]); + Status nRetVal = g_device.invoke(LINK_COMMAND_DESTROY_FW_STREAM, args); + if (nRetVal == STATUS_OK) + { + printf("Successfully destroyed stream %u\n\n", args.id); + } + else + { + printf("Failed to destroy stream: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int StartStream(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n\n", argv[0]); + return -1; + } + + XnCommandStartStream args; + args.id = MyAtoi(argv[1]); + Status nRetVal = g_device.invoke(LINK_COMMAND_START_FW_STREAM, args); + if (nRetVal == STATUS_OK) + { + printf("Successfully started stream %u\n\n", args.id); + } + else + { + printf("Failed to start stream %u: %s\n\n", args.id, OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int StopStream(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n\n", argv[0]); + return -1; + } + + XnCommandStopStream args; + args.id = MyAtoi(argv[1]); + Status nRetVal = g_device.invoke(LINK_COMMAND_STOP_FW_STREAM, args); + if (nRetVal == STATUS_OK) + { + printf("Successfully stopped stream %u\n\n", args.id); + } + else + { + printf("Failed to stop stream %u: %s\n\n", args.id, OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int PrintModes(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + printf("Shows a list of supported map output modes for the specified stream.\n\n"); + return -1; + } + + XnFwStreamVideoMode modes[50]; + XnCommandGetFwStreamVideoModeList args; + args.videoModes = modes; + args.count = sizeof(modes)/sizeof(modes[0]); + args.streamId = MyAtoi(argv[1]); + + Status nRetVal = g_device.invoke(LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE_LIST, args); + if (nRetVal != STATUS_OK) + { + printf("Failed getting video modes list for stream %d: %s\n\n", args.streamId, OpenNI::getExtendedError()); + return -2; + } + + printf("Got %u modes:\n", args.count); + for (uint32_t i = 0; i < args.count; i++) + { + printf("\t[%u] %s\n", i, fwVideoModeToString(modes[i])); + } + printf("\n"); + + return 0; +} + +int SetMode(int argc, const char* argv[]) +{ + if (argc < 7) + { + printf("Usage: %s \n", argv[0]); + printf("Sets the video mode of the specified stream.\n"); + printf("Allowed formats: "); + for (int i = 1; i <= XN_FW_PIXEL_FORMAT_BAYER8; ++i) + printf("%s, ", fwPixelFormatToName((XnFwPixelFormat)i)); + printf("\n"); + printf("Allowed compressions: "); + for (int i = 0; i <= XN_FW_COMPRESSION_12_BIT_PACKED; ++i) + printf("%s, ", fwCompressionTypeToName((XnFwCompressionType)i)); + printf("\n\n"); + return -1; + } + + XnCommandSetFwStreamVideoMode args; + args.streamId = MyAtoi(argv[1]); + args.videoMode.m_nXRes = MyAtoi(argv[2]); + args.videoMode.m_nYRes = MyAtoi(argv[3]); + args.videoMode.m_nFPS = MyAtoi(argv[4]); + args.videoMode.m_nPixelFormat = fwPixelFormatNameToType(argv[5]); + args.videoMode.m_nCompression = fwCompressionNameToType(argv[6]); + + Status nRetVal = g_device.invoke(LINK_COMMAND_SET_FW_STREAM_VIDEO_MODE, args); + if (nRetVal == XN_STATUS_OK) + { + printf("Successfully set video mode.\n\n"); + } + else + { + printf("Failed to set video mode: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int PrintMode(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + printf("Shows the current map output mode of the specified stream.\n\n"); + return -1; + } + + XnCommandSetFwStreamVideoMode args; + args.streamId = MyAtoi(argv[1]); + + Status nRetVal = g_device.invoke(LINK_COMMAND_GET_FW_STREAM_VIDEO_MODE, args); + if (nRetVal == XN_STATUS_OK) + { + printf("Video mode of stream %u: %s\n\n", args.streamId, fwVideoModeToString(args.videoMode)); + } + else + { + printf("Failed to set video mode: %s\n\n", OpenNI::getExtendedError()); + } + + return 0; +} + +xnl::Array& GetI2CDeviceList() +{ + static xnl::Array s_i2cDevices; + + if (s_i2cDevices.GetSize() == 0) + { + XnI2CDeviceInfo devices[20]; + + XnCommandGetI2CDeviceList args; + args.devices = devices; + args.count = sizeof(devices)/sizeof(devices[0]); + + if (STATUS_OK != g_device.invoke(PS_COMMAND_GET_I2C_DEVICE_LIST, args)) + { + printf("Failed getting device list: %s\n\n", OpenNI::getExtendedError()); + } + else + { + s_i2cDevices.SetData(args.devices, args.count); + } + } + + return s_i2cDevices; +} + +XnStatus GetI2CDeviceIDFromName(const char* deviceName, uint32_t* result) +{ + XnStatus nRetVal = XN_STATUS_NO_MATCH; + + xnl::Array& devices = GetI2CDeviceList(); + + nRetVal = XN_STATUS_NO_MATCH; + for (XnUInt32 i = 0; i < devices.GetSize() && nRetVal==XN_STATUS_NO_MATCH; i++) + { + if(xnOSStrCaseCmp(devices[i].name, deviceName) == 0) + { + nRetVal = XN_STATUS_OK; + *result = (XnUInt8)devices[i].id; + } + } + + return nRetVal; +} + +int WriteI2C(int argc, const char* argv[]) +{ + if (argc < 6) + { + printf("Usage: %s
[Mask]\n", argv[0]); + printf("Note - each parameter may be in hex, indicated by an '0x' prefix.\n\n"); + return -1; + } + + XnCommandI2C args; + + //Try to get parse ID by name, and then by number + args.deviceID = 0; + if (GetI2CDeviceIDFromName(argv[1], &args.deviceID) == XN_STATUS_NO_MATCH) + args.deviceID = MyAtoi(argv[1]); + + args.addressSize = MyAtoi(argv[2]); + args.address = MyAtoi(argv[3]); + args.valueSize = MyAtoi(argv[4]); + args.value = MyAtoi(argv[5]); + args.mask = (argc > 6) ? MyAtoi(argv[6]) : 0xFFFFFFFF; + + Status nRetVal = g_device.invoke(PS_COMMAND_I2C_WRITE, args); + if (nRetVal == STATUS_OK) + { + printf("Successfully written I2C value.\n\n"); + } + else + { + printf("Failed to write I2C value: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int ReadI2C(int argc, const char* argv[]) +{ + if (argc < 5) + { + printf("Usage: %s
\n", argv[0]); + printf("Note - each parameter may be in hex, indicated by an '0x' prefix.\n\n"); + return -1; + } + + XnCommandI2C args; + + //Try to get parse ID by name, and then by number + args.deviceID = 0; + if (GetI2CDeviceIDFromName(argv[1], &args.deviceID) == XN_STATUS_NO_MATCH) + args.deviceID = MyAtoi(argv[1]); + + args.addressSize = MyAtoi(argv[2]); + args.address = MyAtoi(argv[3]); + args.valueSize = MyAtoi(argv[4]); + args.value = 0; + + Status nRetVal = g_device.invoke(PS_COMMAND_I2C_READ, args); + if (nRetVal == STATUS_OK) + { + printf("Successfully read I2C value: %u (0x%X)\n\n", args.value, args.value); + } + else + { + printf("Failed to read I2C value: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int WriteAHB(int argc, const char* argv[]) +{ + if (argc < 5) + { + printf("Usage: %s
\n", argv[0]); + printf("Note - each parameter may be in hex, indicated by an '0x' prefix.\n\n"); + return -1; + } + + XnCommandAHB args; + args.address = MyAtoi(argv[1]); + args.value = MyAtoi(argv[2]); + args.offsetInBits = MyAtoi(argv[3]); + args.widthInBits = MyAtoi(argv[4]); + + Status nRetVal = g_device.invoke(PS_COMMAND_AHB_WRITE, args); + if (nRetVal == STATUS_OK) + { + printf("Successfully written AHB value.\n\n"); + } + else + { + printf("Failed to write AHB value: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int ReadAHB(int argc, const char* argv[]) +{ + if (argc < 4) + { + printf("Usage: %s
\n", argv[0]); + printf("Note - each parameter may be in hex, indicated by an '0x' prefix.\n\n"); + return -1; + } + + XnCommandAHB args; + args.address = MyAtoi(argv[1]); + args.offsetInBits = MyAtoi(argv[2]); + args.widthInBits = MyAtoi(argv[3]); + + Status nRetVal = g_device.invoke(PS_COMMAND_AHB_READ, args); + if (nRetVal == XN_STATUS_OK) + { + printf("Successfully read AHB value: %u (0x%X)\n\n", args.value, args.value); + } + else + { + printf("Failed to read I2C value: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int SoftReset(int /*argc*/, const char* /*argv*/[]) +{ + printf("Resetting device..."); + Status nRetVal = g_device.invoke(PS_COMMAND_SOFT_RESET, NULL, 0); + printf("\n"); + if (nRetVal == STATUS_OK) + { + printf("Successfully executed Soft Reset.\n\n"); + } + else + { + printf("Failed to execute soft reset: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int HardReset(int /*argc*/, const char* /*argv*/[]) +{ + printf("Resetting device..."); + Status nRetVal = g_device.invoke(PS_COMMAND_POWER_RESET, NULL, 0); + printf("\n"); + if (nRetVal == STATUS_OK) + { + printf("Successfully executed Hard Reset.\n\n"); + printf("**********************************************\n"); + printf("Warning: console must be restarted!\n"); + printf("**********************************************\n\n"); + } + else + { + printf("Failed to execute hard reset: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int Emitter(int argc, const char* argv[]) +{ + if ((argc < 2) || + ((xnOSStrCaseCmp(argv[1], "on") != 0) && (xnOSStrCaseCmp(argv[1], "off") != 0))) + { + printf("Usage: %s \n\n", argv[0]); + return -1; + } + const XnChar* strEmitterActive = argv[1]; + XnBool bEmitterActive = (xnOSStrCaseCmp(strEmitterActive, "on") == 0); + Status nRetVal = g_device.setProperty(LINK_PROP_EMITTER_ACTIVE, bEmitterActive); + if (nRetVal == STATUS_OK) + { + printf("Emitter is now %s.\n\n", strEmitterActive); + } + else + { + printf("Failed to set emitter %s: %s\n\n", strEmitterActive, OpenNI::getExtendedError()); + } + + return nRetVal; +} + +xnl::Array& GetLogMaskList() +{ + static xnl::Array s_masks; + + if (s_masks.GetSize() == 0) + { + XnFwLogMask masks[20]; + XnCommandGetLogMaskList args; + args.masks = masks; + args.count = sizeof(masks)/sizeof(masks[0]); + + if (STATUS_OK != g_device.invoke(PS_COMMAND_GET_LOG_MASK_LIST, args)) + { + printf("Failed getting masks list: %s\n\n", OpenNI::getExtendedError()); + } + else + { + s_masks.SetData(args.masks, args.count); + } + } + + return s_masks; +} + +XnStatus GetLogIDFromName(const char* mask, uint32_t* result) +{ + XnStatus nRetVal = XN_STATUS_NO_MATCH; + + xnl::Array& masks = GetLogMaskList(); + + nRetVal = XN_STATUS_NO_MATCH; + for (XnUInt32 i = 0; i < masks.GetSize() && nRetVal==XN_STATUS_NO_MATCH; i++) + { + if(xnOSStrCaseCmp(masks[i].name, mask) == 0) + { + nRetVal = XN_STATUS_OK; + *result = (XnUInt8)masks[i].id; + } + } + + return nRetVal; +} + +int Log(int argc, const char* argv[]) +{ + XnStatus nRetVal = XN_STATUS_OK; + bool isOnOffCommnad = (argc == 2) && ((xnOSStrCaseCmp(argv[1], "on") == 0) || (xnOSStrCaseCmp(argv[1], "off") == 0)); + bool isStartStopCommnad = (argc == 3) && ((xnOSStrCaseCmp(argv[1], "open") == 0) || (xnOSStrCaseCmp(argv[1], "close") == 0)); + + if (!isOnOffCommnad && !isStartStopCommnad) + { + printf("Usage: %s or \n\n", argv[0]); + return -1; + } + + if (isStartStopCommnad) + { + //Try to get parse ID by name, and then by number + uint32_t logID = 0; + if (GetLogIDFromName(argv[1], &logID) == XN_STATUS_NO_MATCH) + { + logID = (XnUInt8)MyAtoi(argv[2]); + } + + //Start command + XnCommandSetLogMaskState args; + args.mask = logID; + args.enabled = xnOSStrCaseCmp(argv[1], "open") == 0; + if (STATUS_OK == g_device.invoke(PS_COMMAND_SET_LOG_MASK_STATE, args)) + { + printf("Sent %s command for log #%d", argv[1], (int)logID); + } + else + { + printf("Failed to send %s command for log #%d: %s\n\n", argv[1], (int)logID, OpenNI::getExtendedError()); + } + } + //on/off command + else + { + const XnChar* strLogOn = argv[1]; + XnBool bLogOn = (xnOSStrCaseCmp(strLogOn, "on") == 0); + + if (bLogOn) + { + nRetVal = g_device.invoke(PS_COMMAND_START_LOG, NULL, 0); + } + else + { + nRetVal = g_device.invoke(PS_COMMAND_STOP_LOG, NULL, 0); + } + + if (nRetVal == STATUS_OK) + { + if (bLogOn) + { + printf("Saving firmware log to 'Log'\n\n"); + } + else + { + printf("Firmware log is now off\n\n"); + } + } + else + { + printf("Failed to set log %s: %s\n\n", strLogOn, OpenNI::getExtendedError()); + } + } + + return nRetVal; +} + +int Script(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n\n", argv[0]); + return -1; + } + + return RunScript(argv[1]); +} + +int PrintBootStatus(int /*argc*/, const char* /*argv*/[]) +{ + XnBootStatus bootStatus; + Status nRetVal = g_device.getProperty(LINK_PROP_BOOT_STATUS, &bootStatus); + if (nRetVal != STATUS_OK) + { + printf("Failed to get boot status: %s\n\n", OpenNI::getExtendedError()); + return nRetVal; + } + else + { + enum{MAX_STR_LEN = 256}; + char bootZoneStr[MAX_STR_LEN], bootZoneErr[MAX_STR_LEN]; + + //Helper macro to write enum names + #define CASE_ENUM_TOSTRING(enumValue, enumStr, targetStr) \ + case enumValue: sprintf(targetStr, "%s", enumStr); break; + + //Get XnLinkBootZone string + switch(bootStatus.zone){ + CASE_ENUM_TOSTRING(XN_ZONE_FACTORY, "FACTORY_ZONE", bootZoneStr) + CASE_ENUM_TOSTRING(XN_ZONE_UPDATE, "UPDATE_ZONE", bootZoneStr) + default: + sprintf(bootZoneStr, "Unexpected - %d",bootStatus.zone); + } + + //Get XnLinkBootErrorCode string + switch(bootStatus.errorCode){ + CASE_ENUM_TOSTRING(XN_BOOT_OK, "BOOT_OK", bootZoneErr) + CASE_ENUM_TOSTRING(XN_BOOT_BAD_CRC, "BAD_CRC", bootZoneErr) + CASE_ENUM_TOSTRING(XN_BOOT_UPLOAD_IN_PROGRESS, "UPLOAD_IN_PROGRESS", bootZoneErr) + CASE_ENUM_TOSTRING(XN_BOOT_FW_LOAD_FAILED, "FW_LOAD_FAILED", bootZoneErr) + default: + sprintf(bootZoneErr, "Unexpected - %d",bootStatus.errorCode); + } + + printf("Zone: %s\nError code: %s\n\n", bootZoneStr, bootZoneErr); + + return nRetVal; + } +} + +int PrintLogFilesList(int /*argc*/, const char* /*argv*/[]) +{ + xnl::Array& masks = GetLogMaskList(); + + for (XnUInt32 i = 0; i < masks.GetSize(); ++i) + { + printf("%4u %s\n", masks[i].id, masks[i].name); + } + + printf("\n"); + + return 0; +} + +int PrintI2CList(int /*argc*/, const char* /*argv*/[]) +{ + xnl::Array& deviceList = GetI2CDeviceList(); + for (XnUInt32 i = 0; i < deviceList.GetSize(); ++i) + { + printf("%4u %s\n", deviceList[i].id, deviceList[i].name); + } + + return 0; +} + +int PrintBistList(int /*argc*/, const char* /*argv*/[]) +{ + XnBistInfo tests[20]; + XnCommandGetBistList args; + args.tests = tests; + args.count = sizeof(tests)/sizeof(tests[0]); + + if (STATUS_OK != g_device.invoke(PS_COMMAND_GET_BIST_LIST, args)) + { + printf("Failed getting tests list: %s\n\n", OpenNI::getExtendedError()); + return 1; + } + else + { + for (XnUInt32 i = 0; i < args.count; ++i) + { + printf("%4u %s\n", tests[i].id, tests[i].name); + } + + printf("\n"); + } + + return 0; +} + +int RunBist(int argc, const char* argv[]) +{ + Status nRetVal = STATUS_OK; + + if (argc < 2) + { + printf("Usage: %s ALL | ...\n\n", argv[0]); + return -1; + } + + xnl::BitSet requestedTests; + + if (xnOSStrCaseCmp(argv[1], "ALL") == 0) + { + XnBistInfo tests[20]; + XnCommandGetBistList args; + args.tests = tests; + args.count = sizeof(tests)/sizeof(tests[0]); + + if (STATUS_OK != g_device.invoke(PS_COMMAND_GET_BIST_LIST, args)) + { + printf("Failed getting tests list: %s\n\n", OpenNI::getExtendedError()); + return -2; + } + + for (XnUInt32 i = 0; i < args.count; ++i) + { + requestedTests.Set(tests[i].id, TRUE); + } + } + else + { + for (int i = 1; i < argc; ++i) + { + requestedTests.Set(MyAtoi(argv[i]), TRUE); + } + } + + XnUInt8 response[512]; + XnCommandExecuteBist args; + args.extraData = response; + + for (XnUInt32 i = 0; i < requestedTests.GetSize(); ++i) + { + if (!requestedTests.IsSet(i)) + { + continue; + } + + printf("Executing test %u...\n", i); + args.id = i; + args.extraDataSize = sizeof(response); + nRetVal = g_device.invoke(PS_COMMAND_EXECUTE_BIST, args); + if (nRetVal != STATUS_OK) + { + printf("\nFailed to execute: %s\n\n", OpenNI::getExtendedError()); + return nRetVal; + } + + printf("Test %u ", i); + + if (args.errorCode != 0) + { + printf("Failed (error code 0x%04X).", args.errorCode); + } + else + { + printf("Passed."); + } + + printf("\n"); + + // extra data + if (args.extraDataSize > 0) + { + printf("Extra Data: "); + for (XnUInt32 j = 0; j < args.extraDataSize; ++j) + { + printf("%02X ", args.extraData[j]); + } + printf("\n"); + } + } + + printf("\n"); + + return (XN_STATUS_OK); +} + +int Quit(int /*argc*/, const char* /*argv*/[]) +{ + g_continue = FALSE; + return 0; +} + +int UsbInterface(int argc, const char* argv[]) +{ + Status nRetVal = STATUS_OK; + + if (argc == 1) + { + XnUsbInterfaceType type; + nRetVal = g_device.getProperty(PS_PROPERTY_USB_INTERFACE, &type); + if (nRetVal != STATUS_OK) + { + printf("Failed to get interface: %s\n\n", OpenNI::getExtendedError()); + return -3; + } + + for (size_t i = 0; i < sizeof(g_usbInterfaceNames)/sizeof(g_usbInterfaceNames[0]); ++i) + { + if (g_usbInterfaceNames[i].type == type) + { + printf("Current USB alternative interface is %s (%d)\n\n", g_usbInterfaceNames[i].name, type); + return 0; + } + } + + printf("Unknown USB interface: %d\n\n", type); + return -4; + } + else if (argc == 2) + { + for (size_t i = 0; i < sizeof(g_usbInterfaceNames)/sizeof(g_usbInterfaceNames[0]); ++i) + { + if (xnOSStrCaseCmp(g_usbInterfaceNames[i].name, argv[1]) == 0) + { + nRetVal = g_device.setProperty(PS_PROPERTY_USB_INTERFACE, g_usbInterfaceNames[i].type); + if (nRetVal != STATUS_OK) + { + printf("Failed to set interface: %s\n\n", OpenNI::getExtendedError()); + return -3; + } + else + { + return 0; + } + } + } + } + else + { + printf("Usage: %s [ANY|ISO|BULK]\n\n", argv[0]); + return -4; + } + + return 0; +} + +int FormatZone(int argc, const char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + printf("Note - each parameter may be in hex, indicated by an '0x' prefix.\n\n"); + return -1; + } + + XnUInt32 nZone = MyAtoi(argv[1]); + Status nRetVal = g_device.invoke(PS_COMMAND_FORMAT_ZONE, nZone); + if (nRetVal == STATUS_OK) + { + printf("Successfully formatZone.\n\n"); + } + else + { + printf("Failed to format Zone value: %s\n\n", OpenNI::getExtendedError()); + } + + return nRetVal; +} + +int UsbTest(int argc, const char* argv[]) +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (argc < 2) + { + printf("Usage: %s \n\n", argv[0]); + return -1; + } + + XnUsbTestEndpointResult endpoints[10]; + XnCommandUsbTest args; + args.seconds = MyAtoi(argv[1]); + args.endpointCount = sizeof(endpoints)/sizeof(endpoints[0]); + args.endpoints = endpoints; + + nRetVal = g_device.invoke(PS_COMMAND_USB_TEST, args); + if (nRetVal != STATUS_OK) + { + printf("Failed to perform USB test: %s\n\n", OpenNI::getExtendedError()); + return -3; + } + + printf("USB Test done:\n"); + for (XnUInt32 i = 0; i < args.endpointCount; ++i) + { + printf("\tEndpoint %u - Avg. Bandwidth: %.3f KB/s, Lost Packets: %u\n", i, args.endpoints[i].averageBytesPerSecond / 1000., args.endpoints[i].lostPackets); + } + + return XN_STATUS_OK; +} + +int TestAll(int /*argc*/, const char* /*argv*/[]) +{ + XnChar strCommand[256]; + xnOSStrCopy(strCommand, "Bist ALL", sizeof(strCommand)); + RunCommand(strCommand); + xnOSStrCopy(strCommand, "UsbTest 1", sizeof(strCommand)); + RunCommand(strCommand); + + return XN_STATUS_OK; +} + +void RegisterCommands() +{ + RegisterCommand("Help", Help); +// RegisterCommand("Versions", ComponentsVersions); + RegisterCommand("BeginUpload", BeginUpload); + RegisterCommand("Upload", Upload); + RegisterCommand("EndUpload", EndUpload); + RegisterCommand("Dir", Dir); + RegisterCommand("Download", Download); + RegisterCommand("FWVersion", PrintFirmwareVersion); + RegisterCommand("BootStatus", PrintBootStatus); + RegisterCommand("DumpStream", DumpStream); + RegisterCommand("DumpEP", DumpEP); + RegisterCommand("Enum", EnumerateStreams); + RegisterCommand("Create", CreateStream); + RegisterCommand("Destroy", DestroyStream); + RegisterCommand("Start", StartStream); + RegisterCommand("Stop", StopStream); + RegisterCommand("Modes", PrintModes); + RegisterCommand("SetMode", SetMode); + RegisterCommand("GetMode", PrintMode); + RegisterCommand("I2CList", PrintI2CList); + RegisterCommand("WriteI2C", WriteI2C); + RegisterCommand("ReadI2C", ReadI2C); + RegisterCommand("WriteAHB", WriteAHB); + RegisterCommand("ReadAHB", ReadAHB); + RegisterCommand("SoftReset", SoftReset); + RegisterCommand("HardReset", HardReset); + RegisterCommand("Emitter", Emitter); + RegisterCommand("Log", Log); + RegisterCommand("LogList", PrintLogFilesList); + RegisterCommand("Script", Script); + RegisterCommand("BistList", PrintBistList); + RegisterCommand("Bist", RunBist); + RegisterCommand("UsbInterface", UsbInterface); + RegisterCommand("FormatZone", FormatZone); + RegisterCommand("UsbTest", UsbTest); + RegisterCommand("TestAll", TestAll); + RegisterCommand("Quit", Quit); + RegisterCommand("Bye", Quit); + RegisterCommand("Exit", Quit); +} + +int main(int argc, char* argv[]) +{ + Status nRetVal = STATUS_OK; + const XnChar* strScriptFile = NULL; + XnUInt16 nProductID = 0; + XnBool bQuit = FALSE; + +// printf("PSLinkConsole version %s\n", XN_PS_VERSION_STRING); + + XnInt32 nArgIndex = 1; + while (nArgIndex < argc) + { + if (argv[nArgIndex][0] == '-') + { + if (xnOSStrCaseCmp(argv[nArgIndex], "-product") == 0) + { + ++nArgIndex; + nProductID = (XnUInt16)MyAtoi(argv[nArgIndex++]); + } + else if (xnOSStrCaseCmp(argv[nArgIndex], "-script") == 0) + { + ++nArgIndex; + strScriptFile = argv[nArgIndex++]; + } + else if (xnOSStrCaseCmp(argv[nArgIndex], "-help") == 0) + { + printf("USAGE\n"); + printf("\t%s [-transport ] [-product ] [-script ] [-help]\n", argv[0]); + printf("OPTIONS\n"); + printf("\t-transport \n"); + printf("\t\tOpen a device from a specific transport, either USB or from a specific IP and port number.\n"); + printf("\t\tWhen omitted, USB will be used.\n"); + printf("\t-product \n"); + printf("\t\tOpen only devices with a specific product ID. By default, any device can be opened.\n"); + printf("\t-script \n"); + printf("\t\tRun a script file once connected.\n"); + printf("\t-help\n"); + printf("\t\tDisplay this information.\n"); + return 0; + } + else + { + printf("Unknown option: %s\n. Run %s -help for usage.\n", argv[nArgIndex], argv[0]); + return -1; + } + } + else + { + printf("Unknown option: %s\n. Run %s -help for usage.\n", argv[nArgIndex], argv[0]); + return -1; + } + } + + OpenNI::setLogConsoleOutput(true); + OpenNI::setLogMinSeverity(0); + + Status rc = OpenNI::initialize(); + if (rc != STATUS_OK) + { + printf("Failed to initialize OpenNI. Extended info: %s\n", OpenNI::getExtendedError()); + return -2; + } + + XnUInt32 nWaitTimeRemaining = WAIT_FOR_DEVICE_TIMEOUT; + + Array devices; + OpenNI::enumerateDevices(&devices); + + const char* uri = NULL; + + while (uri == NULL && nWaitTimeRemaining > 0) + { + nWaitTimeRemaining = XN_MAX(0, (XnInt32)(nWaitTimeRemaining - WAIT_FOR_DEVICE_CHECK_INTERVAL_MS)); + + // check if the requested device is connected + for (int i = 0; i < devices.getSize(); ++i) + { + if (nProductID == 0 || devices[i].getUsbProductId() == nProductID) + { + uri = devices[i].getUri(); + break; + } + } + + if (uri == NULL) + { + xnOSSleep(WAIT_FOR_DEVICE_CHECK_INTERVAL_MS); + } + } + + if (uri == NULL) + { + printf("Device not found (after %u milliseconds)\n", WAIT_FOR_DEVICE_TIMEOUT); + XN_ASSERT(FALSE); + return -3; + } + + printf("Device found, connecting...\n"); + + nRetVal = g_device._openEx(uri, "lr"); + if (nRetVal != STATUS_OK) + { + printf("Failed to open device. Extended info: %s\n", OpenNI::getExtendedError()); + return -4; + } + + //Prime Client is now connected :) + + RegisterCommands(); + + if (strScriptFile != NULL) + { + if (XN_STATUS_OK != RunScript(strScriptFile)) + { + // error is returned only if script could not be run, not if any command in it failed. + return -5; + } + + if (bQuit) + { + return 0; + } + } + + ExecuteCommandsFromStream(stdin, TRUE); + + g_device.close(); + OpenNI::shutdown(); + + return 0; +} diff --git a/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj b/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj new file mode 100644 index 0000000..e6c7c8c --- /dev/null +++ b/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj @@ -0,0 +1,191 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D39A4248-3985-41DE-AFD5-AEC58D29291F} + PSLinkConsole + Win32Proj + + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + MultiByte + true + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + false + + + + Disabled + ..\;..\Protocols\XnLinkProto;..\LinkProtoLib;..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + EditAndContinue + 4250;4127;%(DisableSpecificWarnings) + true + true + + + OpenNI2.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + MachineX86 + true + + + + + X64 + + + Disabled + ..\;..\Protocols\XnLinkProto;..\LinkProtoLib;..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + OpenNI2.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + MachineX64 + true + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + ..\;..\Protocols\XnLinkProto;..\LinkProtoLib;..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + OpenNI2.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + true + true + MachineX86 + true + + + + + X64 + + + /MP %(AdditionalOptions) + MaxSpeed + true + ..\;..\Protocols\XnLinkProto;..\LinkProtoLib;..\..\..\..\Include;..\..\..\..\ThirdParty\PSCommon\XnLib\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level4 + ProgramDatabase + 4250;4127;%(DisableSpecificWarnings) + true + + + OpenNI2.lib;XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin/$(Platform)-$(Configuration);%(AdditionalLibraryDirectories) + true + Console + true + true + MachineX64 + true + + + + + + + + + \ No newline at end of file diff --git a/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj.filters b/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj.filters new file mode 100644 index 0000000..8210d26 --- /dev/null +++ b/Source/Drivers/PSLink/PSLinkConsole/PSLinkConsole.vcxproj.filters @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + \ No newline at end of file diff --git a/Source/Drivers/PSLink/PrimeClient.cpp b/Source/Drivers/PSLink/PrimeClient.cpp new file mode 100644 index 0000000..bd871c2 --- /dev/null +++ b/Source/Drivers/PSLink/PrimeClient.cpp @@ -0,0 +1,644 @@ +#include "PrimeClient.h" +#include "XnLinkInputStreamsMgr.h" +#include +#include "IConnectionFactory.h" +#include "XnPsVersion.h" + +#include +#include "XnOSCpp.h" + +#define XN_MASK_PRIME_CLIENT "PrimeClient" + +namespace xn +{ + +const XnUInt32 PrimeClient::MAX_COMMAND_SIZE = 0x40000; +const XnUInt32 PrimeClient::CONT_STREAM_PREDEFINED_BUFFER_SIZE = 0x40000; + +PrimeClient::PrimeClient() +{ + m_pConnectionFactory = NULL; + m_bInitialized = FALSE; + m_bConnected = FALSE; + + /* Global properties */ + xnOSMemSet(&m_fwVersion, 0, sizeof(m_fwVersion)); + xnOSMemSet(&m_protocolVersion, 0, sizeof(m_protocolVersion)); + m_nHWVersion = 0; + xnOSMemSet(&m_strSerialNumber, 0, sizeof(m_strSerialNumber)); + m_nFWLogStreamID = XN_LINK_STREAM_ID_NONE; +} + +PrimeClient::~PrimeClient() +{ + Shutdown(); +} + +XnStatus PrimeClient::Init(const XnChar* strConnString, XnTransportType transportType) +{ + XnStatus nRetVal = XN_STATUS_OK; + m_pConnectionFactory = CreateConnectionFactory(transportType); + XN_VALIDATE_ALLOC_PTR(m_pConnectionFactory); + nRetVal = m_pConnectionFactory->Init(strConnString); + XN_IS_STATUS_OK_LOG_ERROR("Init connection factory", nRetVal); + xnOSStrCopy(m_strConnectionString, strConnString, sizeof(m_strConnectionString)); + + nRetVal = m_linkInputStreamsMgr.Init(); + XN_IS_STATUS_OK_LOG_ERROR("Init link input streams mgr", nRetVal); + nRetVal = m_linkOutputStreamsMgr.Init(); + XN_IS_STATUS_OK_LOG_ERROR("Init link output streams mgr", nRetVal); + nRetVal = m_linkControlEndpoint.Init(MAX_COMMAND_SIZE, m_pConnectionFactory); + XN_IS_STATUS_OK_LOG_ERROR("Init link control endpoint", nRetVal); + + nRetVal = m_inputDataEndpoints.SetSize(m_pConnectionFactory->GetNumInputDataConnections()); + XN_IS_STATUS_OK_LOG_ERROR("Set size of input data endpoints array", nRetVal); + + m_bInitialized = TRUE; + return XN_STATUS_OK; +} + +void PrimeClient::Shutdown() +{ + if (m_bInitialized) + { + for (XnUInt32 nEndpointID = 0; nEndpointID < m_inputDataEndpoints.GetSize(); nEndpointID++) + { + m_inputDataEndpoints[nEndpointID].Shutdown(); + } + m_outputDataEndpoint.Shutdown(); + + //First shutdown stream managers before control endpoint, because they might need to send a StopStreaming command + m_linkOutputStreamsMgr.Shutdown(); + m_linkInputStreamsMgr.Shutdown(); + m_linkControlEndpoint.Shutdown(); + xnOSSleep(200); //TODO: Get rid of this once we have a disconnection command + m_pConnectionFactory->Shutdown(); + XN_DELETE(m_pConnectionFactory); + m_pConnectionFactory = NULL; + m_bInitialized = FALSE; + } +} + +XnBool PrimeClient::IsInitialized() const +{ + return m_bInitialized; +} + +XnStatus PrimeClient::Connect() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (!m_bConnected) + { + nRetVal = m_linkControlEndpoint.Connect(); + XN_IS_STATUS_OK_LOG_ERROR("Connect link control endpoint", nRetVal); + + // Connect output data endpoint (if any) + //TODO: Connect output data endpoint only on the first time we send anything to device. + nRetVal = ConnectOutputDataEndpoint(); + XN_IS_STATUS_OK_LOG_ERROR("Connect output data endpoint", nRetVal); + + nRetVal = m_linkControlEndpoint.GetSupportedProperties(m_supportedProps); + XN_IS_STATUS_OK_LOG_ERROR("Get supported properties", nRetVal); + + // Get some versions + XnLinkDetailedVersion fwVersion; + nRetVal = m_linkControlEndpoint.GetFWVersion(fwVersion); + XN_IS_STATUS_OK_LOG_ERROR("Get FW version", nRetVal); + m_fwVersion.m_nMajor = fwVersion.m_nMajor; + m_fwVersion.m_nMinor = fwVersion.m_nMinor; + m_fwVersion.m_nMaintenance = fwVersion.m_nMaintenance; + m_fwVersion.m_nBuild = fwVersion.m_nBuild; + xnOSStrCopy(m_fwVersion.m_strModifier, fwVersion.m_strModifier, sizeof(m_fwVersion.m_strModifier)); + + nRetVal = m_linkControlEndpoint.GetProtocolVersion(m_protocolVersion); + XN_IS_STATUS_OK_LOG_ERROR("Get protocol version", nRetVal); + + nRetVal = m_linkControlEndpoint.GetHardwareVersion(m_nHWVersion); + XN_IS_STATUS_OK_LOG_ERROR("Get hardware version", nRetVal); + + nRetVal = m_linkControlEndpoint.GetSerialNumber(m_strSerialNumber, sizeof(m_strSerialNumber)); + XN_IS_STATUS_OK_LOG_ERROR("Get serial number", nRetVal); + + xnLogInfo(XN_MASK_PRIME_CLIENT, "Prime Client is now connected."); + LogVersions(); + m_bConnected = TRUE; + } + + return XN_STATUS_OK; +} + +void PrimeClient::Disconnect() +{ + for (XnUInt16 i = 0; i < m_inputDataEndpoints.GetSize(); i++) + { + m_inputDataEndpoints[i].Disconnect(); + } + m_linkControlEndpoint.Disconnect(); +} + + +XnBool PrimeClient::IsConnected() const +{ + return m_bConnected; +} + +const LinkInputStream* PrimeClient::GetInputStream(XnUInt16 nStreamID) const +{ + return m_linkInputStreamsMgr.GetInputStream(nStreamID); +} + +LinkInputStream* PrimeClient::GetInputStream(XnUInt16 nStreamID) +{ + return m_linkInputStreamsMgr.GetInputStream(nStreamID); +} + +const XnDetailedVersion& PrimeClient::GetFWVersion() const +{ + return m_fwVersion; +} + +const XnUInt32 PrimeClient::GetHWVersion() const +{ + return m_nHWVersion; +} + +const XnLeanVersion& PrimeClient::GetDeviceProtocolVersion() const +{ + return m_protocolVersion; +} + +const XnChar* PrimeClient::GetSerialNumber() const +{ + return m_strSerialNumber; +} + +const XnStatus PrimeClient::GetComponentsVersions(xnl::Array& componentVersions) +{ + return m_linkControlEndpoint.GetComponentsVersions(componentVersions); +} + +XnStatus PrimeClient::InitOutputStream(XnUInt16 nStreamID, + XnUInt32 nMaxMsgSize, + XnUInt16 nMaxPacketSize, + XnLinkCompressionType compression, + XnStreamFragLevel streamFragLevel) +{ + return m_linkOutputStreamsMgr.InitOutputStream(nStreamID, nMaxMsgSize, nMaxPacketSize, compression, + streamFragLevel, &m_outputDataEndpoint); +} + +void PrimeClient::ShutdownOutputStream(XnUInt16 nStreamID) +{ + m_linkOutputStreamsMgr.ShutdownOutputStream(nStreamID); +} + +void PrimeClient::HandleLinkDataEndpointDisconnection(XnUInt16 /*nEndpointID*/) +{ +} + +XnStatus PrimeClient::SoftReset() +{ + return m_linkControlEndpoint.SoftReset(); +} + +XnStatus PrimeClient::HardReset() +{ + return m_linkControlEndpoint.HardReset(); +} + +XnStatus PrimeClient::WriteI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32 nValue, XnUInt32 nMask) +{ + return m_linkControlEndpoint.WriteI2C(nDeviceID, nAddressSize, nAddress, nValueSize, nValue, nMask); +} + +XnStatus PrimeClient::ReadI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32& nValue) +{ + return m_linkControlEndpoint.ReadI2C(nDeviceID, nAddressSize, nAddress, nValueSize, nValue); +} + +XnStatus PrimeClient::WriteAHB(XnUInt32 nAddress, XnUInt32 nValue, XnUInt8 nBitOffset, XnUInt8 nBitWidth) +{ + return m_linkControlEndpoint.WriteAHB(nAddress, nValue, nBitOffset, nBitWidth); +} + +XnStatus PrimeClient::ReadAHB(XnUInt32 nAddress, XnUInt8 nBitOffset, XnUInt8 nBitWidth, XnUInt32& nValue) +{ + return m_linkControlEndpoint.ReadAHB(nAddress, nBitOffset, nBitWidth, nValue); +} + +XnStatus PrimeClient::EnumerateStreams(xnl::Array& aStreamInfos) +{ + return m_linkControlEndpoint.EnumerateStreams(aStreamInfos); +} + +XnStatus PrimeClient::EnumerateStreams(XnStreamType streamType, xnl::Array& aStreamInfos) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnl::Array aAll; + nRetVal = m_linkControlEndpoint.EnumerateStreams(aAll); + XN_IS_STATUS_OK(nRetVal); + + for (XnUInt32 i = 0; i < aAll.GetSize(); ++i) + { + if ((XnUInt32)aAll[i].type == streamType) + { + aStreamInfos.AddLast(aAll[i]); + } + } + + return XN_STATUS_OK; +} + +XnStatus PrimeClient::CreateInputStream(XnStreamType streamType, const XnChar* strCreationInfo, XnUInt16& nStreamID) +{ + if (!m_linkInputStreamsMgr.HasStreamOfType(streamType,strCreationInfo, nStreamID)) + { + // No stream of this type exists. Create a new one + XnStatus nRetVal = XN_STATUS_OK; + XnUInt16 nEndpointID = 0; + + //Send create stream command + nRetVal = CreateInputStreamImpl((XnLinkStreamType)streamType, strCreationInfo, nStreamID, nEndpointID); + XN_IS_STATUS_OK_LOG_ERROR("Create stream", nRetVal); + + xnLogInfo(XN_MASK_LINK, "Created input stream %u of type '%s' on endpoint %u", + nStreamID, xnLinkStreamTypeToString(streamType), nEndpointID); + } + + // now let the stream manager know that we have another "holder" of the stream + m_linkInputStreamsMgr.RegisterStreamOfType(streamType, strCreationInfo, nStreamID); + + return XN_STATUS_OK; +} + +XnStatus PrimeClient::DestroyInputStream(XnUInt16 nStreamID) +{ + if (m_linkInputStreamsMgr.UnregisterStream(nStreamID)) + { + // we were the last ones "holding" the stream + XnStatus nRetVal = XN_STATUS_OK; + nRetVal = m_linkControlEndpoint.DestroyInputStream(nStreamID); + XN_IS_STATUS_OK_LOG_ERROR("Destroy stream", nRetVal); + m_linkInputStreamsMgr.ShutdownInputStream(nStreamID); + xnLogInfo(XN_MASK_PRIME_CLIENT, "Input stream %u destroyed.", nStreamID); + } + return XN_STATUS_OK; +} + +XnStatus PrimeClient::BeginUploadFileOnControlEP() +{ + return m_linkControlEndpoint.BeginUpload(); +} + +XnStatus PrimeClient::EndUploadFileOnControlEP() +{ + return m_linkControlEndpoint.EndUpload(); +} + +XnStatus PrimeClient::UploadFileOnControlEP(const XnChar* strFileName, XnBool bOverrideFactorySettings) +{ + return m_linkControlEndpoint.UploadFile(strFileName, bOverrideFactorySettings); +} + +XnStatus PrimeClient::FormatZone(XnUInt8 nZone) +{ + return m_linkControlEndpoint.FormatZone(nZone); +} + +XnStatus PrimeClient::ConnectOutputDataEndpoint() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (m_outputDataEndpoint.IsInitialized()) + { + nRetVal = m_outputDataEndpoint.Connect(); + XN_IS_STATUS_OK_LOG_ERROR("Connect output data endpoint", nRetVal); + } + + return XN_STATUS_OK; +} + +XnStatus PrimeClient::GetFileList(xnl::Array& files) +{ + return m_linkControlEndpoint.GetFileList(files); +} + +XnStatus PrimeClient::DownloadFile(XnUInt16 zone, const XnChar* strFirmwareFileName, const XnChar* strTargetFile) +{ + return m_linkControlEndpoint.DownloadFile(zone, strFirmwareFileName, strTargetFile); +} + +XnStatus PrimeClient::SetEmitterActive(XnBool bActive) +{ + return m_linkControlEndpoint.SetEmitterActive(bActive); +} + +void PrimeClient::LogVersions() +{ + static XnBool bVersionsLoggedOnce = FALSE; + + if (!bVersionsLoggedOnce) + { + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Prime Client version:\t%s", XN_PS_BRIEF_VERSION_STRING); + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Host protocol version:\t%u.%u", XN_LINK_PROTOCOL_MAJOR_VERSION, XN_LINK_PROTOCOL_MINOR_VERSION); + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Device Protocol version:\t%u.%u", m_protocolVersion.m_nMajor, m_protocolVersion.m_nMinor); + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Device FW version:\t\t%u.%u.%u.%u-%s", m_fwVersion.m_nMajor, m_fwVersion.m_nMinor, m_fwVersion.m_nMaintenance, m_fwVersion.m_nBuild, m_fwVersion.m_strModifier); + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Device HW version:\t\t0x%04X", m_nHWVersion); + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Device SerialNumber:\t%s", m_strSerialNumber); + bVersionsLoggedOnce = TRUE; + } +} + +XnStatus PrimeClient::OpenFWLogFile(XnUInt8 logID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + //If no stream started, start it + if (m_nFWLogStreamID == XN_LINK_STREAM_ID_NONE) + { + nRetVal = StartFWLog(); + XN_IS_STATUS_OK_LOG_ERROR("Start FWLog stream", nRetVal); + } + + //Get FW Log input stream + LinkInputStream* pFWLogStream = GetInputStream(m_nFWLogStreamID); + if (pFWLogStream == NULL) + { + xnLogError(XN_MASK_PRIME_CLIENT, "FW log input stream is NULL?!"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + //Open the log file + nRetVal = m_linkControlEndpoint.OpenFWLogFile(logID, pFWLogStream->GetStreamID()); + + return nRetVal; +} + +XnStatus PrimeClient::CloseFWLogFile(XnUInt8 logID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + //If no stream started, something is wrong + if (m_nFWLogStreamID == XN_LINK_STREAM_ID_NONE) + { + xnLogError(XN_MASK_PRIME_CLIENT, "No FW log input stream"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + //Get FW Log input stream + LinkInputStream* pFWLogStream = GetInputStream(m_nFWLogStreamID); + if (pFWLogStream == NULL) + { + xnLogError(XN_MASK_PRIME_CLIENT, "FW log input stream is NULL?!"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + //Close the log file + nRetVal = m_linkControlEndpoint.CloseFWLogFile(logID, pFWLogStream->GetStreamID()); + + return nRetVal; +} + +XnStatus PrimeClient::StartFWLog() +{ + XnStatus nRetVal = XN_STATUS_OK; + xnl::Array fwLogStreamInfos; + XnUInt16 nEndpointID = 0; + + //Enumerate log streams (there should be exactly one) + nRetVal = EnumerateStreams(XN_LINK_STREAM_TYPE_LOG, fwLogStreamInfos); + XN_IS_STATUS_OK_LOG_ERROR("Enumerate log streams", nRetVal); + if (fwLogStreamInfos.GetSize() == 0) + { + xnLogError(XN_MASK_PRIME_CLIENT, "No FW log stream exists in device"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + if (fwLogStreamInfos.GetSize() > 1) + { + xnLogError(XN_MASK_PRIME_CLIENT, "Only one FW log stream is supported"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + //Create log stream (from first enumeration result) + nRetVal = CreateInputStreamImpl(XN_LINK_STREAM_TYPE_LOG, fwLogStreamInfos[0].creationInfo, m_nFWLogStreamID, nEndpointID); + XN_IS_STATUS_OK_LOG_ERROR("Create log input stream", nRetVal); + LinkInputStream* pFWLogStream = GetInputStream(m_nFWLogStreamID); + if (pFWLogStream == NULL) + { + xnLogError(XN_MASK_PRIME_CLIENT, "FW log input stream is NULL?!"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + //Start the log stream + nRetVal = pFWLogStream->Start(); + XN_IS_STATUS_OK_LOG_ERROR("Start FW Log Stream", nRetVal); + xnLogInfo(XN_MASK_PRIME_CLIENT, "FW Log started on stream %u, endpoint %u", m_nFWLogStreamID, nEndpointID); + + return XN_STATUS_OK; +} + +XnStatus PrimeClient::StopFWLog() +{ + XnStatus nRetVal = XN_STATUS_OK; + if (m_nFWLogStreamID != XN_LINK_STREAM_ID_NONE) + { + //Get FW Log input stream + LinkInputStream* pFWLogStream = GetInputStream(m_nFWLogStreamID); + if (pFWLogStream == NULL) + { + xnLogError(XN_MASK_PRIME_CLIENT, "FW log input stream is NULL?!"); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + //Stop stream + nRetVal = pFWLogStream->Stop(); + XN_IS_STATUS_OK_LOG_ERROR("Stop FW log stream", nRetVal); + + //Destroy stream + nRetVal = DestroyInputStream(m_nFWLogStreamID); + XN_IS_STATUS_OK_LOG_ERROR("Destroy input stream", nRetVal); + //Mark stream as unused + m_nFWLogStreamID = XN_LINK_STREAM_ID_NONE; + } + return XN_STATUS_OK; +} + +XnStatus PrimeClient::CreateInputStreamImpl(XnLinkStreamType streamType, const XnChar* strCreationInfo, XnUInt16& nStreamID, XnUInt16& nEndpointID) +{ + XnStatus nRetVal = XN_STATUS_OK; + + nRetVal = m_linkControlEndpoint.CreateInputStream(streamType, strCreationInfo, nStreamID, nEndpointID); + XN_IS_STATUS_OK_LOG_ERROR("Create stream on device", nRetVal); + + if (nEndpointID > m_inputDataEndpoints.GetSize()) + { + xnLogError(XN_MASK_PRIME_CLIENT, "Stream %u was created on non-existing endpoint %u", nStreamID, nEndpointID); + XN_ASSERT(FALSE); + return XN_STATUS_ERROR; + } + + if (!m_inputDataEndpoints[nEndpointID].IsInitialized()) + { + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Initializing input data endpoint 0x%X...", nEndpointID); + nRetVal = m_inputDataEndpoints[nEndpointID].Init(nEndpointID, + m_pConnectionFactory, + &m_linkInputStreamsMgr, + this); + XN_IS_STATUS_OK_LOG_ERROR("Init input data endpoint", nRetVal); + } + + //Initialize input stream + nRetVal = m_linkInputStreamsMgr.InitInputStream(&m_linkControlEndpoint, streamType, nStreamID, &m_inputDataEndpoints[nEndpointID]); + XN_IS_STATUS_OK_LOG_ERROR("Init input stream", nRetVal); + return XN_STATUS_OK; +} + +#define CHECK_TOKEN(pToken, strLine, pFile) \ + if (pToken == NULL) \ + { \ + xnLogError(XN_MASK_PRIME_CLIENT, "Preset file corrupt: line '%s' is not in the correct format!", strLine); \ + XN_ASSERT(FALSE); \ + fclose(pFile); \ + return XN_STATUS_CORRUPT_FILE; \ + } + +XnStatus PrimeClient::RunPresetFile(const XnChar* strFileName) +{ + XnStatus nRetVal = XN_STATUS_OK; + + xnLogVerbose(XN_MASK_PRIME_CLIENT, "Executing preset file '%s'...", strFileName); + + // Check that file exists + XnBool bFileExists; + nRetVal = xnOSDoesFileExist(strFileName, &bFileExists); + XN_IS_STATUS_OK(nRetVal); + + if (!bFileExists) + { + xnLogError(XN_MASK_PRIME_CLIENT, "File '%s' does not exist", strFileName); + return XN_STATUS_OS_FILE_NOT_FOUND; + } + + FILE* pFile = fopen(strFileName, "r"); + XN_ASSERT(pFile != NULL); + + XnChar strLine[1024]; + + // read header + if (NULL == fgets(strLine, sizeof(strLine), pFile)) + { + xnLogError(XN_MASK_PRIME_CLIENT, "File '%s' is empty - no header", strFileName); + return XN_STATUS_ERROR; + } + + XnUInt32 nAddress; + XnUInt32 nValue; + XnUInt8 nBitOffset; + XnUInt8 nBitWidth; + + for (;;) + { + // read a line + if (NULL == fgets(strLine, sizeof(strLine), pFile)) + { + // end of file reached + break; + } + + // skip empty lines + if (xnOSStrCmp(strLine, "\n") == 0 || xnOSStrCmp(strLine, "\r\n") == 0) + { + continue; + } + + // block name + XnChar* pToken = strtok(strLine, ","); + CHECK_TOKEN(pToken, strLine, pFile); + + // reg name + pToken = strtok(NULL, ","); + CHECK_TOKEN(pToken, strLine, pFile); + + // Address + pToken = strtok(NULL, ","); + CHECK_TOKEN(pToken, strLine, pFile); + sscanf(pToken, "0x%x", &nAddress); + + // field name + pToken = strtok(NULL, ","); + CHECK_TOKEN(pToken, strLine, pFile); + + // bit offset + pToken = strtok(NULL, ","); + CHECK_TOKEN(pToken, strLine, pFile); + nBitOffset = (XnUInt8)atoi(pToken); + + // bit width + pToken = strtok(NULL, ","); + CHECK_TOKEN(pToken, strLine, pFile); + nBitWidth = (XnUInt8)atoi(pToken); + + // value + pToken = strtok(NULL, ","); + CHECK_TOKEN(pToken, strLine, pFile); + sscanf(pToken, "0x%x", &nValue); + + // execute + nRetVal = WriteAHB(nAddress, nValue, nBitOffset, nBitWidth); + if (nRetVal != XN_STATUS_OK) + { + fclose(pFile); + return nRetVal; + } + } + + fclose(pFile); + + xnLogInfo(XN_MASK_PRIME_CLIENT, "Preset file '%s' was executed", strFileName); + + return (XN_STATUS_OK); +} + +XnStatus PrimeClient::GetSupportedBistTests(xnl::Array& supportedTests) +{ + return m_linkControlEndpoint.GetSupportedBistTests(supportedTests); +} + +XnStatus PrimeClient::GetSupportedI2CDevices(xnl::Array& supportedDevices) +{ + return m_linkControlEndpoint.GetSupportedI2CDevices(supportedDevices); +} + +XnStatus PrimeClient::GetSupportedLogFiles(xnl::Array& supportedFiles) +{ + return m_linkControlEndpoint.GetSupportedLogFiles(supportedFiles); +} + +XnStatus PrimeClient::ExecuteBist(XnUInt32 nID, uint32_t& errorCode, uint32_t& extraDataSize, uint8_t* extraData) +{ + return m_linkControlEndpoint.ExecuteBistTests(nID, errorCode, extraDataSize, extraData); +} + +XnBool PrimeClient::IsPropertySupported(XnUInt16 propID) +{ + XnUInt32 nInterface = (propID >> 8); + XnUInt32 nProp = (propID & 0x0F); + return (nInterface < m_supportedProps.GetSize() && m_supportedProps[nInterface].IsSet(nProp)); +} + +XnStatus PrimeClient::GetBootStatus(XnBootStatus& bootStatus) +{ + return m_linkControlEndpoint.GetBootStatus(bootStatus); +} + +} diff --git a/Source/Drivers/PSLink/PrimeClient.h b/Source/Drivers/PSLink/PrimeClient.h new file mode 100644 index 0000000..e95c3f0 --- /dev/null +++ b/Source/Drivers/PSLink/PrimeClient.h @@ -0,0 +1,127 @@ +#ifndef __PRIMECLIENT_H__ +#define __PRIMECLIENT_H__ + +#include "XnLinkProtoLibDefs.h" +#include "XnLinkControlEndpoint.h" +#include "XnLinkInputDataEndpoint.h" +#include "XnLinkOutputDataEndpoint.h" +#include "XnLinkInputStreamsMgr.h" +#include "XnLinkOutputStreamsMgr.h" +#include "PrimeClientDefs.h" +#include "XnShiftToDepth.h" +#include +#include +#include +#include + +namespace xn +{ + +class ISyncIOConnection; +class IOutputConnection; +class IConnectionFactory; + +class PrimeClient : + virtual public ILinkDataEndpointNotifications +{ +public: + PrimeClient(); + virtual ~PrimeClient(); + + /* Initialization and shutdown */ + virtual XnStatus Init(const XnChar* strConnString, XnTransportType transportType); + virtual void Shutdown(); + virtual XnBool IsInitialized() const; + virtual XnStatus Connect(); + virtual XnBool IsConnected() const; + virtual void Disconnect(); + + /* Global Properties */ + virtual const XnDetailedVersion& GetFWVersion() const; + virtual const XnUInt32 GetHWVersion() const; + virtual const XnLeanVersion& GetDeviceProtocolVersion() const; + virtual const XnChar* GetSerialNumber() const; + virtual const XnStatus GetComponentsVersions(xnl::Array& componentVersions); + const XnChar* GetConnectionString() const { return m_strConnectionString; } + + /* Global Device Commands */ + virtual XnStatus GetBootStatus(XnBootStatus& bootStatus); + virtual XnStatus GetSupportedI2CDevices(xnl::Array& supportedDevices); + virtual XnStatus WriteI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32 nValue, XnUInt32 nMask); + virtual XnStatus ReadI2C(XnUInt8 nDeviceID, XnUInt8 nAddressSize, XnUInt32 nAddress, XnUInt8 nValueSize, XnUInt32& nValue); + virtual XnStatus WriteAHB(XnUInt32 nAddress, XnUInt32 nValue, XnUInt8 nBitOffset, XnUInt8 nBitWidth); + virtual XnStatus ReadAHB(XnUInt32 nAddress, XnUInt8 nBitOffset, XnUInt8 nBitWidth, XnUInt32& nValue); + virtual XnStatus SoftReset(); + virtual XnStatus HardReset(); + virtual XnStatus SetEmitterActive(XnBool bActive); + virtual XnStatus StartFWLog(); + virtual XnStatus StopFWLog(); + virtual XnStatus OpenFWLogFile(XnUInt8 logID); + virtual XnStatus CloseFWLogFile(XnUInt8 logID); + virtual XnStatus GetSupportedLogFiles(xnl::Array& supportedFiles); + + virtual XnStatus RunPresetFile(const XnChar* strFileName); + virtual XnStatus GetSupportedBistTests(xnl::Array& supportedTests); + virtual XnStatus ExecuteBist(XnUInt32 nID, uint32_t& errorCode, uint32_t& extraDataSize, uint8_t* extraData); + virtual XnStatus FormatZone(XnUInt8 nZone); + //TODO: Implement Get emitter active + + /* Stream Management */ + virtual XnStatus EnumerateStreams(xnl::Array& aStreamInfos); + virtual XnStatus EnumerateStreams(XnStreamType streamType, xnl::Array& aStreamInfos); + virtual XnStatus CreateInputStream(XnStreamType nodeType, const XnChar* strCreationInfo, XnUInt16& nStreamID); + + virtual XnStatus DestroyInputStream(XnUInt16 nStreamID); + virtual LinkInputStream* GetInputStream(XnUInt16 nStreamID); + virtual const LinkInputStream* GetInputStream(XnUInt16 nStreamID) const; + + virtual XnStatus InitOutputStream(XnUInt16 nStreamID, + XnUInt32 nMaxMsgSize, + XnUInt16 nMaxPacketSize, + XnLinkCompressionType compression, + XnStreamFragLevel streamFragLevel); + + virtual void ShutdownOutputStream(XnUInt16 nStreamID); + + virtual XnStatus BeginUploadFileOnControlEP(); + virtual XnStatus EndUploadFileOnControlEP(); + virtual XnStatus UploadFileOnControlEP(const XnChar* strFileName, XnBool bOverrideFactorySettings); + virtual XnStatus GetFileList(xnl::Array& files); + virtual XnStatus DownloadFile(XnUInt16 zone, const XnChar* strFirmwareFileName, const XnChar* strTargetFile); + + virtual void HandleLinkDataEndpointDisconnection(XnUInt16 nEndpointID); + +protected: + virtual XnStatus ConnectOutputDataEndpoint(); + virtual IConnectionFactory* CreateConnectionFactory(XnTransportType transportType) = 0; +// XnStatus GetStreamBufferSize(XnUInt16 nStreamID, XnProductionNodeType nodeType, XnStreamFragLevel streamFragLevel, XnUInt32& nBufferSize); + void LogVersions(); + XnStatus CreateInputStreamImpl(XnLinkStreamType streamType, const XnChar* strCreationInfo, XnUInt16& nStreamID, XnUInt16& nEndpointID); + XnBool IsPropertySupported(XnUInt16 propID); + LinkControlEndpoint m_linkControlEndpoint; + LinkOutputDataEndpoint m_outputDataEndpoint; + IConnectionFactory* m_pConnectionFactory; + LinkInputStreamsMgr m_linkInputStreamsMgr; + LinkOutputStreamsMgr m_linkOutputStreamsMgr; + +private: + static const XnUInt32 MAX_COMMAND_SIZE; + static const XnUInt32 CONT_STREAM_PREDEFINED_BUFFER_SIZE; + + XnBool m_bInitialized; + volatile XnBool m_bConnected; + xnl::Array m_inputDataEndpoints; + XnBool m_bAnyDataEndpointConnected; + XnUInt16 m_nFWLogStreamID; + XnChar m_strConnectionString[XN_FILE_MAX_PATH]; + + xnl::Array m_supportedProps; + XnDetailedVersion m_fwVersion; + XnLeanVersion m_protocolVersion; + XnUInt32 m_nHWVersion; + XnChar m_strSerialNumber[XN_SERIAL_NUMBER_SIZE]; +}; + +} + +#endif // __PRIMECLIENT_H__ diff --git a/Source/Drivers/PSLink/PrimeClientDefs.h b/Source/Drivers/PSLink/PrimeClientDefs.h new file mode 100644 index 0000000..e678f23 --- /dev/null +++ b/Source/Drivers/PSLink/PrimeClientDefs.h @@ -0,0 +1,40 @@ +#ifndef __PRIMECLIENTDEFS_H__ +#define __PRIMECLIENTDEFS_H__ + +#ifdef PRIMECLIENT_EXPORTS +#define XN_PRIME_CLIENT_CPP_API XN_API_EXPORT +#else +#define XN_PRIME_CLIENT_CPP_API XN_API_IMPORT +#endif + +//--------------------------------------------------------------------------- +// Macros for working with properties +//--------------------------------------------------------------------------- +#define EXACT_PROP_SIZE(size, type) \ + if ((size_t)size != sizeof(type)) return ONI_STATUS_BAD_PARAMETER; +#define EXACT_PROP_SIZE_DO(size, type) \ + if ((size_t)size != sizeof(type)) + +#define ENSURE_PROP_SIZE(size, minType) \ + if (((size_t)size < sizeof(minType)) || ((size != 1) && (size != 2) && (size != 4) && (size != 8))) return ONI_STATUS_BAD_PARAMETER; +#define ENSURE_PROP_SIZE_DO(size, minType) \ + if (((size_t)size < sizeof(minType)) || ((size != 1) && (size != 2) && (size != 4) && (size != 8))) + +#define ASSIGN_PROP_VALUE_INT(pDst, dstSize, value) \ + if (dstSize == 8) *(int64_t*)pDst = (int64_t)(value); \ + else if(dstSize == 4) *(int32_t*)pDst = (int32_t)(value); \ + else if(dstSize == 2) *(short*) pDst = (short) (value); \ + else if(dstSize == 1) *(char*) pDst = (char) (value); + +#define ASSIGN_PROP_VALUE_FLOAT(pDst, dstSize, value) \ + if (dstSize == 8) *(XnDouble*)pDst = (XnDouble)(value); \ + else if(dstSize == 4) *(XnFloat*) pDst = (XnFloat) (value); + +#define GET_PROP_VALUE_INT(dest, data, dataSize) \ + if (dataSize == 8) dest = (int)*(int64_t*)data; \ + else if (dataSize == 4) dest = (int)*(int32_t*)data; \ + else if (dataSize == 2) dest = (int)*(int16_t*)data; \ + else if (dataSize == 1) dest = (int)*(int8_t*)data; \ + else return ONI_STATUS_BAD_PARAMETER; + +#endif // __PRIMECLIENTDEFS_H__ diff --git a/Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkDefs.h b/Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkDefs.h new file mode 100644 index 0000000..42991ce --- /dev/null +++ b/Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkDefs.h @@ -0,0 +1,457 @@ +#ifndef __XNLINKDEFS_H__ +#define __XNLINKDEFS_H__ + +/* Version */ +#define XN_LINK_PROTOCOL_MAJOR_VERSION 0 +#define XN_LINK_PROTOCOL_MINOR_VERSION 56 + +/* Magic numbers */ +#define XN_LINK_MAGIC 0x5350 //"PS" +#define XN_LINK_STREAM_ID_NONE 0x0 +#define XN_LINK_STREAM_ID_INVALID 0xFFFF + +/* Max sizes */ +#define XN_LINK_MAX_STREAMS 32 //TODO: This is not really the max number of streams! Stream ID is 14 bit, so theoretically max id is 0x3FFF +#define XN_LINK_MAX_CREATION_INFO_LENGTH 80 +#define XN_LINK_MAX_LOG_MASK_LENGTH 16 +#define XN_LINK_SERIAL_NUMBER_SIZE 32 +#define XN_LINK_MAX_BIST_NAME_LENGTH 32 +#define XN_LINK_MAX_FILE_NAME_LENGTH 32 +#define XN_LINK_MAX_VERSION_MODIFIER_LENGTH 16 +#define XN_LINK_MAX_COMPONENT_NAME_LENGTH 32 +#define XN_LINK_MAX_VERSION_LENGTH 32 +#define XN_LINK_MAX_I2C_DEVICE_NAME_LENGTH 32 +#define XN_LINK_MAX_LOG_FILE_NAME_LENGTH 32 + +/* Interface ID's */ +typedef enum XnLinkInterfaceID +{ + XN_LINK_INTERFACE_FW_MGMT = 0x00, + XN_LINK_INTERFACE_LINK = 0x01, + XN_LINK_INTERFACE_SYS_MGMT = 0x02, + XN_LINK_INTERFACE_DATA_STREAMING = 0x03, + XN_LINK_INTERFACE_RESERVED1 = 0x04, + XN_LINK_INTERFACE_MAP_GENERATOR = 0x05, + XN_LINK_INTERFACE_STREAM_MGMT = 0x06, + XN_LINK_INTERFACE_PROPS = 0x07, + XN_LINK_INTERFACE_RESERVED2 = 0x08, + XN_LINK_INTERFACE_HANDS_GENERATOR = 0x09, + XN_LINK_INTERFACE_S2D = 0x0A, + XN_LINK_INTERFACE_GESTURE_GENERATOR = 0x0B, + XN_LINK_INTERFACE_USER_GENERATOR = 0x0C, + XN_LINK_INTERFACE_DEPTH_GENERATOR = 0x0D, + XN_LINK_INTERFACE_MIRROR = 0x0E, + XN_LINK_INTERFACE_ALTERNATIVE_VIEW_POINT = 0x0F, + XN_LINK_INTERFACE_CROPPING = 0x10, + XN_LINK_INTERFACE_USER_POSITION = 0x11, + XN_LINK_INTERFACE_SKELETON = 0x12, + XN_LINK_INTERFACE_POSE_DETECTION = 0x13, + XN_LINK_INTERFACE_LOCK_AWARE = 0x14, + XN_LINK_INTERFACE_ERROR_STATE = 0x15, + XN_LINK_INTERFACE_FRAME_SYNC = 0x16, + XN_LINK_INTERFACE_DEVICE_IDENTIFICATION = 0x17, + XN_LINK_INTERFACE_BRIGHTNESS = 0x18, + XN_LINK_INTERFACE_CONTRAST = 0x19, + XN_LINK_INTERFACE_HUE = 0x1A, + XN_LINK_INTERFACE_SATURATION = 0x1B, + XN_LINK_INTERFACE_SHARPNESS = 0x1C, + XN_LINK_INTERFACE_GAMMA = 0x1D, + XN_LINK_INTERFACE_COLOR_TEMPERATURE = 0x1E, + XN_LINK_INTERFACE_BACKLIGHT_COMPENSATION = 0x1F, + XN_LINK_INTERFACE_GAIN = 0x20, + XN_LINK_INTERFACE_PAN = 0x21, + XN_LINK_INTERFACE_TILT = 0x22, + XN_LINK_INTERFACE_ROLL = 0x23, + XN_LINK_INTERFACE_ZOOM = 0x24, + XN_LINK_INTERFACE_EXPOSURE = 0x25, + XN_LINK_INTERFACE_IRIS = 0x26, + XN_LINK_INTERFACE_FOCUS = 0x27, + XN_LINK_INTERFACE_LOW_LIGHT_COMPENSATION = 0x28, + XN_LINK_INTERFACE_ANTI_FLICKER = 0x29, + XN_LINK_INTERFACE_HAND_TOUCHING_FOV_EDGE = 0x2A, + XN_LINK_INTERFACE_PROJECTOR_MGMT = 0x2B, + XN_LINK_INTERFACE_INVALID = 0xFF, //Signifies an invalid interface ID +} XnLinkInterfaceID; + +/* Message Types - Control messages */ +typedef enum XnLinkMsgType +{ + XN_LINK_MSG_NONE = 0x0000, + +//XN_LINK_INTERFACE_FW_MGMT - Firmware management messages - group 0x00 + XN_LINK_MSG_RESERVED_1 = 0x0001, + XN_LINK_MSG_UPLOAD_FILE = 0x0002, + XN_LINK_MSG_RESERVED_3 = 0x0003, + XN_LINK_MSG_RESERVED_4 = 0x0004, + XN_LINK_MSG_BEGIN_UPLOAD = 0x0005, + //Deprecated - XN_LINK_MSG_BEGIN_RECEIVE_DATA = 0x0006, + //Deprecated - XN_LINK_MSG_END_RECEIVE_DATA = 0x0007, + XN_LINK_MSG_END_UPLOAD = 0x0008, + XN_LINK_MSG_GET_FILE_LIST = 0x0009, + XN_LINK_MSG_DOWNLOAD_FILE = 0x000A, // in: XnLinkDownloadFileParams, out: file data + XN_LINK_MSG_FORMAT_ZONE = 0x000B, + +//XN_LINK_INTERFACE_LINK - Link messages - group 0x01 + XN_LINK_MSG_CONTINUE_REPONSE = 0x0101, + +//XN_LINK_INTERFACE_SYS_MGMT - System management messages - group 0x02 + XN_LINK_MSG_SOFT_RESET = 0x0201, + XN_LINK_MSG_HARD_RESET = 0x0202, + XN_LINK_MSG_WRITE_I2C = 0x0203, + XN_LINK_MSG_READ_I2C = 0x0204, + XN_LINK_MSG_WRITE_AHB = 0x0205, + XN_LINK_MSG_READ_AHB = 0x0206, + XN_LINK_MSG_EXECUTE_BIST_TESTS = 0x0207, // In: XnLinkExecuteBistParams Out: XnLinkExecuteBistResponse + XN_LINK_MSG_GET_ACC_CURENT_PARAM = 0x0208, + XN_LINK_MSG_SET_PWM_DC = 0x0209, + XN_LINK_MSG_START_USB_TEST = 0x020A, + XN_LINK_MSG_STOP_USB_TEST = 0x020B, + XN_LINK_MSG_START_LOG_FILE = 0x020C, + XN_LINK_MSG_STOP_LOG_FILE = 0x020D, + +//XN_LINK_INTERFACE_DATA_STREAMING - Data streaming messages - group 0x03 + XN_LINK_MSG_START_STREAMING = 0x0300, + XN_LINK_MSG_DATA = 0x0301, //Sent on data endpoint, not control + XN_LINK_MSG_STOP_STREAMING = 0x0302, + XN_LINK_MSG_START_STREAMING_MULTI = 0x0303, + XN_LINK_MSG_STOP_STREAMING_MULTI = 0x0304, + +//XN_LINK_INTERFACE_RESERVED1 + +//XN_LINK_INTERFACE_MAP_GENERATOR - Map generator messages - group 0x05 + XN_LINK_MSG_GET_CAMERA_INTRINSICS = 0x0501, // Out: XnLinkCameraIntrinsics + +//XN_LINK_INTERFACE_STREAM_MGMT - Stream management messages - group 0x06 + XN_LINK_MSG_ENUMERATE_STREAMS = 0x0601, + XN_LINK_MSG_CREATE_STREAM = 0x0602, + XN_LINK_MSG_DESTROY_STREAM = 0x0603, + +//XN_LINK_INTERFACE_PROPS - Set/Get property messages - group 0x07 + XN_LINK_MSG_GET_PROP = 0x0701, + XN_LINK_MSG_SET_PROP = 0x0702, + XN_LINK_MSG_SET_MULTI_PROPS = 0x0703, + +//XN_LINK_INTERFACE_HANDS_GENERATOR - HandGenerator messages - group 0x09 + XN_LINK_MSG_START_TARCKING_HAND = 0x0901, + XN_LINK_MSG_STOP_TARCKING_HAND = 0x0902, + XN_LINK_MSG_STOP_TARCKING_ALL_HANDS = 0x0903, + +//XN_LINK_INTERFACE_S2D - Shift to depth messages - group 0x0A + XN_LINK_MSG_GET_S2D_CONFIG = 0x0A01, + +//XN_LINK_INTERFACE_GESTURE_GENERATOR - GestureGenerator messages - group 0x0B + XN_LINK_MSG_ENUMERATE_AVAILABLE_GESTURES = 0x0B01, + XN_LINK_MSG_ENUMERATE_ACTIVE_GESTURES = 0x0B02, + XN_LINK_MSG_ACTIVATE_GESTURE = 0x0B03, + XN_LINK_MSG_DEACTIVATE_GESTURE = 0x0B04, + +//XN_LINK_INTERFACE_USER_GENERATOR - UserGenerator messages - group 0x0C + +// XN_LINK_INTERFACE_MIRROR - Mirror messages - group 0x0E + +// XN_LINK_INTERFACE_ALTERNATIVE_VIEW_POINT messages - group 0x0F + +// XN_LINK_INTERFACE_CROPPING messages - group 0x10 + +// XN_LINK_INTERFACE_USER_POSITION messages - group 0x11 + +// XN_LINK_INTERFACE_SKELETON messages - group 0x12 + XN_LINK_MSG_SET_SKELETON_PROFILE = 0x1201, + XN_LINK_MSG_SET_SKELETON_JOINT_STATE = 0x1202, + XN_LINK_MSG_REQUEST_CALIBRATION = 0x1203, + XN_LINK_MSG_ABORT_CALIBRATION = 0x1204, + XN_LINK_MSG_SAVE_SKELETON_CALIBRATION_DATA = 0x1205, + XN_LINK_MSG_LOAD_SKELETON_CALIBRATION_DATA = 0x1206, + XN_LINK_MSG_CLEAR_SKELETON_CALIBRATION_DATA = 0x1207, + XN_LINK_MSG_IS_SKELETON_CALIBRATION_SLOT_FREE = 0x1208, + XN_LINK_MSG_GET_SKELETON_CALIBRATION_DATA = 0x1209, + XN_LINK_MSG_SET_SKELETON_CALIBRATION_DATA = 0x120A, + XN_LINK_MSG_START_SKELETON_TRACKING = 0x120B, + XN_LINK_MSG_STOP_SKELETON_TRACKING = 0x120C, + XN_LINK_MSG_RESET_SKELETON_TRACKING = 0x120D, + +// XN_LINK_INTERFACE_POSE_DETECTION messages - group 0x13 + XN_LINK_MSG_START_POSE_DETECTION = 0x130E, + XN_LINK_MSG_STOP_POSE_DETECTION = 0X130F, + +// XN_LINK_INTERFACE_LOCK_AWARE messages - group 0x14 + +// XN_LINK_INTERFACE_ERROR_STATE messages - group 0x15 + +// XN_LINK_INTERFACE_FRAME_SYNC messages - group 0x16 + +// XN_LINK_INTERFACE_DEVICE_IDENTIFICATION messages - group 0x17 + +// XN_LINK_INTERFACE_BRIGHTNESS messages - group 0x18 + +// XN_LINK_INTERFACE_CONTRAST messages - group 0x19 + +// XN_LINK_INTERFACE_HUE messages - group 0x1A + +// XN_LINK_INTERFACE_SATURATION messages - group 0x1B + +// XN_LINK_INTERFACE_SHARPNESS messages - group 0x1C + +// XN_LINK_INTERFACE_GAMMA messages - group 0x1D + +// XN_LINK_INTERFACE_COLOR_TEMPERATURE messages - group 0x1E + +// XN_LINK_INTERFACE_BACKLIGHT_COMPENSATION messages - group 0x1F + +// XN_LINK_INTERFACE_GAIN messages - group 0x20 + +// XN_LINK_INTERFACE_PAN messages - group 0x21 + +// XN_LINK_INTERFACE_TILT messages - group 0x22 + +// XN_LINK_INTERFACE_ROLL messages - group 0x23 + +// XN_LINK_INTERFACE_ZOOM messages - group 0x24 + +// XN_LINK_INTERFACE_EXPOSURE messages - group 0x25 + +// XN_LINK_INTERFACE_IRIS messages - group 0x26 + +// XN_LINK_INTERFACE_FOCUS messages - group 0x27 + +// XN_LINK_INTERFACE_LOW_LIGHT_COMPENSATION messages - group 0x28 + +// XN_LINK_INTERFACE_ANTI_FLICKER messages - group 0x29 + +// XN_LINK_INTERFACE_HAND_TOUCHING_FOV_EDGE messages - group 0x2A + +// XN_LINK_INTERFACE_PROJECTOR_MGMT messages - group 0x2B + + XN_LINK_MSG_INVALID = 0xFFFF, +} XnLinkMsgType; + +/* Enumerations */ +typedef enum XnLinkFragmentation +{ + XN_LINK_FRAG_MIDDLE = 0x00, + XN_LINK_FRAG_BEGIN = 0x01, + XN_LINK_FRAG_END = 0x02, + XN_LINK_FRAG_SINGLE = 0x03, +} XnLinkFragmentation; + +typedef enum XnLinkResponseCode +{ + XN_LINK_RESPONSE_OK = 0, //The command succeeded + XN_LINK_RESPONSE_PENDING = 1, //The command is an async command, and the host should poll the device for completion. + XN_LINK_RESPONSE_BAD_FILE_TYPE = 2, //The host requested to download a file type which does not exist. + XN_LINK_RESPONSE_CMD_ERROR = 3, //General command error + XN_LINK_RESPONSE_CMD_NOT_SUPPORTED = 4, //The host sent a command which is not supported. + XN_LINK_RESPONSE_BAD_CMD_SIZE = 5, //The host has sent a command with the wrong size of parameters data. + XN_LINK_RESPONSE_BAD_PARAMETERS = 6, //The host send some bad parameters. A list of the offsets of the bad parameters will be returned. + XN_LINK_RESPONSE_CORRUPT_PACKET = 7, //The device has received a packet which does not conform to the protocol. + XN_LINK_RESPONSE_RESERVED1 = 8, + XN_LINK_RESPONSE_RESERVED2 = 9, + XN_LINK_RESPONSE_RESERVED3 = 10, + XN_LINK_RESPONSE_RESERVED4 = 11, + XN_LINK_RESPONSE_RESERVED5 = 12, + XN_LINK_RESPONSE_RESERVED6 = 13, + XN_LINK_RESPONSE_FILE_CORRUPT = 14, //The file being loaded is corrupt + XN_LINK_RESPONSE_BAD_CRC = 15, //Bad CRC + XN_LINK_RESPONSE_INCORRECT_SIZE = 16, //The received size is incorrect + XN_LINK_RESPONSE_INPUT_BUFFER_OVERFLOW = 17, //Input buffer overflow +} XnLinkResponseCode; + +typedef enum XnLinkStreamType +{ + XN_LINK_STREAM_TYPE_NONE = 0x0000, + XN_LINK_STREAM_TYPE_COLOR = 0x0001, + XN_LINK_STREAM_TYPE_IR = 0x0002, + XN_LINK_STREAM_TYPE_SHIFTS = 0x0003, + XN_LINK_STREAM_TYPE_AUDIO = 0x0004, + XN_LINK_STREAM_TYPE_DY = 0x0005, + + XN_LINK_STREAM_TYPE_LOG = 0x0008, + ///////////// + XN_LINK_STREAM_TYPE_USER = 0x000A, + XN_LINK_STREAM_TYPE_HANDS = 0x000B, + XN_LINK_STREAM_TYPE_GESTURES = 0x000C, + + XN_LINK_STREAM_TYPE_INVALID = 0xFFFF, +} XnLinkStreamType; + +typedef enum +{ + XN_LINK_PIXEL_FORMAT_NONE = 0x0000, + XN_LINK_PIXEL_FORMAT_SHIFTS_9_3 = 0x0001, + XN_LINK_PIXEL_FORMAT_GRAYSCALE16 = 0x0002, + XN_LINK_PIXEL_FORMAT_YUV422 = 0x0003, + XN_LINK_PIXEL_FORMAT_BAYER8 = 0x0004, +} XnLinkPixelFormat; + +typedef enum +{ + XN_LINK_COMPRESSION_NONE = 0x0000, + XN_LINK_COMPRESSION_8Z = 0x0001, + XN_LINK_COMPRESSION_16Z = 0x0002, + XN_LINK_COMPRESSION_24Z = 0x0003, + XN_LINK_COMPRESSION_6_BIT_PACKED = 0x0004, + XN_LINK_COMPRESSION_10_BIT_PACKED = 0x0005, + XN_LINK_COMPRESSION_11_BIT_PACKED = 0x0006, + XN_LINK_COMPRESSION_12_BIT_PACKED = 0x0007, +} XnLinkCompressionType; + +typedef enum XnLinkStreamFragLevel +{ + XN_LINK_STREAM_FRAG_LEVEL_NONE = 0, + XN_LINK_STREAM_FRAG_LEVEL_FRAMES = 1, + XN_LINK_STREAM_FRAG_LEVEL_CONTINUOUS = 2, +} XnLinkStreamFragLevel; + +typedef enum XnLinkPropType +{ + XN_LINK_PROP_TYPE_NONE = 0x0000, + XN_LINK_PROP_TYPE_INT = 0x0001, + XN_LINK_PROP_TYPE_REAL = 0x0002, + XN_LINK_PROP_TYPE_STRING = 0x0003, + XN_LINK_PROP_TYPE_GENERAL = 0x0004, + XN_LINK_PROP_TYPE_MAX = XN_LINK_PROP_TYPE_GENERAL, +} XnLinkPropType; + +typedef enum XnLinkPropID +{ + XN_LINK_PROP_ID_NONE = 0x0000, + + //FW management properties - group 0x00 (All of these are global, not related to specific stream) + XN_LINK_PROP_ID_CONTROL_MAX_PACKET_SIZE = 0x0001, //Int property (read only property) + XN_LINK_PROP_ID_FW_VERSION = 0x0002, //General property, holds XnLinkDetailedVersion (read only property) + XN_LINK_PROP_ID_PROTOCOL_VERSION = 0x0003, //General property, holds XnLinkLeanVersion (read only property) + XN_LINK_PROP_ID_SUPPORTED_MSG_TYPES = 0x0004, //General property, holds XnLinkIDSet (read only property) + XN_LINK_PROP_ID_SUPPORTED_PROPS = 0x0005, //General property, holds XnLinkIDSet (read only property) + XN_LINK_PROP_ID_HW_VERSION = 0x0006, //Int property (read only property) + XN_LINK_PROP_ID_SERIAL_NUMBER = 0x0007, //General property, holds XnLinkSerialNumber (read only property) + XN_LINK_PROP_ID_EMITTER_ACTIVE = 0x0008, //Int property, 1/0 for emitter on/off + XN_LINK_PROP_ID_COMPONENT_VERSIONS = 0x0009, //General property, holds XnLinkComponentVersionsList (read only property) + XN_LINK_PROP_ID_BOOT_STATUS = 0x000A, //General property, holds XnLinkBootStatus (read only property) + + //System management properties - group 0x02 (All of these are global, not related to specific stream) + XN_LINK_PROP_ID_SUPPORTED_BIST_TESTS = 0x0201, //General property, holds XnLinkSupportedBistTests + XN_LINK_PROP_ID_SUPPORTED_I2C_DEVICES = 0x0202, //General property, holds XnLinkSupportedI2CDevices + XN_LINK_PROP_ID_SUPPORTED_LOG_FILES = 0x0203, //General property, holds XnLinkSupportedLogFiles + + //Map generator properties - group 0x05 + XN_LINK_PROP_ID_SUPPORTED_VIDEO_MODES = 0x0501, //General property, holds XnLinkSupportedVideoModes + XN_LINK_PROP_ID_VIDEO_MODE = 0x0502, //General property, holds XnLinkVideoMode + + //Stream management properties - group 0x06 + XN_LINK_PROP_ID_STREAM_SUPPORTED_INTERFACES = 0x0601, //General property, holds XnLinkBitSet with values from XnLinkInterfaceID (read only property) + XN_LINK_PROP_ID_STREAM_FRAG_LEVEL = 0x0602, //Int property, holds XnLinkStreamFragLevel + + //HandGenerator properties - group 0x09 + XN_LINK_PROP_ID_HAND_SMOOTHING = 0x0901, //Real property + + // SKELETON properties - group 0x12 + XN_LINK_PROP_ID_SUPPORTED_SKELETON_JOINTS = 0x1201, //General property, holds XnLinkBitSet of XnSkeletonJoint values + XN_LINK_PROP_ID_SUPPORTED_SKELETON_PROFILES = 0x1202, //General property, holds XnLinkBitSet of XnSkeletonProfile values + XN_LINK_PROP_ID_NEEDED_CALIBRATION_POSE = 0x1203, //Int property, holds XnLinkPoseType + XN_LINK_PROP_ID_ACTIVE_JOINTS = 0x1204, //General property, holds XnLinkBitSet of XnSkeletonJoint values + XN_LINK_PROP_ID_SKELETON_SMOOTHING = 0x1205, //Real + + // POSE_DETECTION properties - group 0x13 + XN_LINK_PROP_ID_SUPPORTED_POSES = 0x1301, //Int property, holds a bit set of XnLinkPoseType + + // Mirror properties - group 0x0E + XN_LINK_PROP_ID_MIRROR = 0x0E01, //Int property, 0 = Mirror off, 1 = Mirror on + + // CROPPING properties - group 0x10 + XN_LINK_PROP_ID_CROPPING = 0x1001, //General property, holds XnLinkCropping + + // GAIN properties - group 0x20 + XN_LINK_PROP_ID_GAIN = 0x2001, //Int property + + // PROJECTOR_MGMT messages - group 0x2B + XN_LINK_PROP_ID_PROJECTOR_PULSE = 0x2B01, //General property, holds XnLinkProjectorPulse + XN_LINK_PROP_ID_PROJECTOR_POWER = 0x2B02, //Int property + + XN_LINK_PROP_ID_INVALID = 0xFFFF, //Indicates invalid property ID +} XnLinkPropID; + +typedef enum XnLinkFileFlags +{ + XN_LINK_FILE_FLAG_BAD_CRC = 0x0001, +} XnLinkFileFlags; + +typedef enum XnLinkGestureType +{ + XN_LINK_GESTURE_NONE = 0x0000, + XN_LINK_GESTURE_RAISE_HAND = 0x0001, + XN_LINK_GESTURE_WAVE = 0x0002, + XN_LINK_GESTURE_CLICK = 0x0003, + XN_LINK_GESTURE_MOVING_HAND = 0x0004, +} XnLinkGestureType; + +typedef enum XnLinkGestureEventType +{ + XN_LINK_GESTURE_EVENT_NONE = 0x0000, + XN_LINK_GESTURE_EVENT_RECOGNIZED = 0x0001, + XN_LINK_GESTURE_EVENT_PROGRESS = 0x0002, + XN_LINK_GESTURE_EVENT_STAGE_COMPLETE = 0x0003, + XN_LINK_GESTURE_EVENT_READY_FOR_NEXT_STAGE = 0x0004, +} XnLinkGestureEventType; + +typedef enum XnLinkUserElementType +{ + XN_LINK_USER_ELEMENT_INVALID = 0x0000, + XN_LINK_USER_ELEMENT_POSE_DETECTION = 0x0001, + XN_LINK_USER_ELEMENT_IN_POSE = 0x0002, + XN_LINK_USER_ELEMENT_CALIBRATION = 0x0003, + XN_LINK_USER_ELEMENT_TRACKING = 0x0004, +} XnLinkUserElementType; + +typedef enum XnLinkPoseType +{ + XN_LINK_POSE_TYPE_NONE = 0, + XN_LINK_POSE_TYPE_PSI = 1 << 0, +} XnLinkPoseType; + +typedef enum XnLinkUsersPixelBLOBFormats +{ + XN_LINK_USERS_PIXELS_UNFORMTED = 0, + XN_LINK_USERS_PIXELS_SEQUENCE_LIST = 1, // Using XnMapSequenceListConverter +} XnLinkUsersPixelBLOBFormats; + +typedef enum XnLinkUserStatus +{ + XN_LINK_USER_STATUS_NONE = 0, + XN_LINK_USER_STATUS_IN_POSE = 1 << 0, + XN_LINK_USER_STATUS_CALIBRATING = 1 << 1, + XN_LINK_USER_STATUS_CALIBRATED = 1 << 2, + XN_LINK_USER_STATUS_TRACKING = 1 << 3, + XN_LINK_USER_STATUS_OUT_OF_SCENE = 1 << 4, +} XnLinkUserStatus; + +typedef enum XnLinkIDSetFormat +{ + XN_LINK_ID_SET_FORMAT_NONE = 0, + XN_LINK_ID_SET_FORMAT_BITSET = 1, +} XnLinkIDSetFormat; + +//Log +typedef enum XnLinkLogCommand +{ + XN_LINK_LOG_COMMAND_OPEN /*clear file when open*/= 0x00, + XN_LINK_LOG_COMMAND_CLOSE = 0x01, + XN_LINK_LOG_COMMAND_WRITE = 0x02, + XN_LINK_LOG_COMMAND_OPEN_APPEND = 0x03, +}XnLinkLogCommand; + +//Boot status +typedef enum XnLinkBootZone +{ + XN_LINK_BOOT_FACTORY_ZONE = 0x0000, + XN_LINK_BOOT_UPDATE_ZONE = 0x0001, +} XnLinkBootZone; + +typedef enum XnLinkBootErrorCode +{ + XN_LINK_BOOT_OK = 0x0000, + XN_LINK_BOOT_BAD_CRC = 0x0001, + XN_LINK_BOOT_UPLOAD_IN_PROGRESS = 0x0002, + XN_LINK_BOOT_FW_LOAD_FAILED = 0x0003, +} XnLinkBootErrorCode; + +#endif // __XNLINKDEFS_H__ diff --git a/Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkProto.h b/Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkProto.h new file mode 100644 index 0000000..647a300 --- /dev/null +++ b/Source/Drivers/PSLink/Protocols/XnLinkProto/XnLinkProto.h @@ -0,0 +1,786 @@ +#ifndef __XNLINKPROTO_H__ +#define __XNLINKPROTO_H__ + +#include +#include "XnLinkDefs.h" + +#if XN_PLATFORM != XN_PLATFORM_ARC +#pragma pack (push, 1) +#endif + + +//----------------------------------------------------------------------- +// Packet Structure +//----------------------------------------------------------------------- +#if XN_PLATFORM_IS_LITTLE_ENDIAN +typedef struct XnLinkPacketHeader +{ + XnUInt16 m_nMagic; + XnUInt16 m_nSize; + XnUInt16 m_nMsgType; + XnUInt16 m_nCID; + XnUInt16 m_nPacketID; + XnUInt16 m_nStreamID : 14; + XnUInt16 m_nFragmentation : 2; //The two most significant bits of these 16 bits are the fragmentation +} XnLinkPacketHeader; +#else +typedef struct XnLinkPacketHeader +{ + XnUInt16 m_nMagic; + XnUInt16 m_nSize; + XnUInt16 m_nMsgType; + XnUInt16 m_nCID; + XnUInt16 m_nPacketID; + XnUInt16 m_nFragmentation : 2; //The two most significant bits of these 16 bits are the fragmentation + XnUInt16 m_nStreamID : 14; +} XnLinkPacketHeader; + +#endif + +typedef struct XnLinkPacket +{ + XnLinkPacketHeader m_packetHeader; + XnUInt8 m_data[1]; +} XnLinkPacket; + +typedef struct XnLinkDataHeader +{ + XnUInt32 m_nTimestampLo; + XnUInt32 m_nTimestampHi; +} XnLinkDataHeader; + + +//----------------------------------------------------------------------- +// Log Header Structure +//----------------------------------------------------------------------- +typedef struct XnLinkLogParam +{ + XnUInt8 m_ID; // 0 for normal log, 1,2... for other logType + XnUInt8 command; // from XnLinkLogCommand. + // 0- write data to file with m_ID, + // 1- open the file with m_ID and Name logFileName + // 2- for close file with m_ID and Name logFileName + XnUInt16 size; //size of all the message (included the Header and data) +} XnLinkLogParam; + + +typedef struct XnLinkLogFileParam +{ + XnUInt8 logFileName[XN_LINK_MAX_LOG_FILE_NAME_LENGTH]; +}XnLinkLogFileParam; + +//----------------------------------------------------------------------- +// Data Elements +//----------------------------------------------------------------------- +typedef struct XnLinkVideoMode +{ + XnUInt16 m_nXRes; + XnUInt16 m_nYRes; + XnUInt16 m_nFPS; + XnUInt8 m_nPixelFormat; // from XnLinkPixelFormat + XnUInt8 m_nCompression; // from XnLinkCompressionType +} XnLinkVideoMode; + +typedef struct +{ + XnUInt32 m_nNumModes; + XnLinkVideoMode m_supportedVideoModes[1]; +} XnLinkSupportedVideoModes; + +typedef struct XnLinkShiftToDepthConfig +{ + /** The zero plane distance in depth units. */ + XnUInt16 nZeroPlaneDistance; + XnUInt16 m_nReserved; + /** The zero plane pixel size */ + XnFloat fZeroPlanePixelSize; + /** The distance between the emitter and the Depth Cmos */ + XnFloat fEmitterDCmosDistance; + /** The maximum possible shift value from this device. */ + XnUInt32 nDeviceMaxShiftValue; + /** The maximum possible depth from this device (as opposed to a cut-off). */ + XnUInt32 nDeviceMaxDepthValue; + + XnUInt32 nConstShift; + XnUInt32 nPixelSizeFactor; + XnUInt32 nParamCoeff; + XnUInt32 nShiftScale; + XnUInt16 nDepthMinCutOff; + XnUInt16 nDepthMaxCutOff; + +} XnLinkShiftToDepthConfig; + +typedef struct XnLinkSerialNumber +{ + XnChar m_strSerialNumber[XN_LINK_SERIAL_NUMBER_SIZE]; +} XnLinkSerialNumber; + +typedef struct XnLinkPoint3D +{ + XnFloat m_fX; + XnFloat m_fY; + XnFloat m_fZ; +} XnLinkPoint3D; + +typedef struct XnLinkBoundingBox3D +{ + XnLinkPoint3D leftBottomNear; + XnLinkPoint3D rightTopFar; +} XnLinkBoundingBox3D; + +typedef struct XnLinkBitSet +{ + XnUInt32 m_nSize; //Size in bytes of encoded data + XnUInt8 m_aData[1]; +} XnLinkBitSet; + +typedef struct XnLinkStreamInfo +{ + XnUInt32 m_nStreamType; + XnChar m_strCreationInfo[XN_LINK_MAX_CREATION_INFO_LENGTH]; + //XnUInt16 m_nStreamID; + //XnUInt16 m_nReserved; +} XnLinkStreamInfo; + +typedef struct XnLinkHandData +{ + XnUInt32 m_nHandID; + XnLinkPoint3D m_position; + XnUInt32 m_touchingFOVEdge; // XnDirection values, ILLEGAL means not touching FOV edge +} XnLinkHandData; + +typedef struct XnLinkHandsData +{ + XnUInt32 m_nHandsCount; + XnLinkHandData m_aHands[1]; +} XnLinkHandsData; + +typedef struct XnLinkGestureRecognizedEventArgs +{ + XnLinkPoint3D m_IDPosition; + XnLinkPoint3D m_EndPosition; +} XnLinkGestureRecognizedEventArgs; + +typedef struct XnLinkGestureProgressEventArgs +{ + XnLinkPoint3D m_position; + XnFloat m_fProgress; +} XnLinkGestureProgressEventArgs; + +typedef struct XnLinkGestureIntermediateStageEventArgs +{ + XnLinkPoint3D m_position; +} XnLinkGestureIntermediateStageEventArgs; + +typedef struct XnLinkGestureEventHeader +{ + XnUInt32 m_nGesture;// Taken from XnLinkGestureType + XnUInt32 m_nGestureEventType;//Taken from XnLinkGestureEventType +} XnLinkGestureEventHeader; + +typedef struct XnLinkGestureDataHeader +{ + XnUInt32 m_nEventsCount; +} XnLinkGestureDataHeader; + +typedef struct XnLinkUserPoseDetectionElement +{ + XnUInt32 m_nPoseID; // Flags taken from XnLinkPoseType + XnUInt32 m_nDetectionStatus; // XnPoseDetectionStatus +} XnLinkUserPoseDetectionElement; + +typedef struct XnLinkUserInPoseElement +{ + XnUInt32 m_nPoseID; // Flags taken from XnLinkPoseType +} XnLinkUserPoseElement; + +typedef struct XnLinkUserCalibrationElement +{ + XnUInt32 m_nCalibrationStatus; // taken from XnCalibrationStatus +} XnLinkUserCalibrationElement; + +typedef struct XnLinkUserJointData +{ + XnUInt32 m_nJointID; + XnLinkPoint3D m_position; + XnFloat m_fPositionConfidence; + XnFloat m_afOrientation[9]; + XnFloat m_fOrientationConfidence; +} XnLinkUserJointData; + +typedef struct XnLinkUserTrackingElement +{ + XnLinkUserJointData m_aJoints[1]; +} XnLinkUserTrackingElement; + +typedef struct XnLinkUserDataElementHeader +{ + XnUInt32 m_nElementType; + XnUInt32 m_nElementSize; +} XnLinkUserDataElementHeader; + +typedef struct XnLinkUserDataElement +{ + XnLinkUserDataElementHeader m_header; + XnUInt8 m_elementData[1]; +} XnLinkUserDataElement; + +typedef struct XnLinkUserDataHeader +{ + XnUInt32 m_nSize; + XnUInt32 m_nUserID; + XnUInt32 m_nUserStatus; // Flags taken from XnLinkUserStatus + XnLinkPoint3D m_centerOfMass; + XnUInt32 m_nDataElements; +} XnLinkUserDataHeader; + +typedef struct XnLinkUserData +{ + XnLinkUserDataHeader m_header; + XnLinkUserDataElement m_aElements[1]; +} XnLinkUserData; + +typedef struct XnLinkUserFrameHeader +{ + XnUInt32 m_nUsersCount; + + XnUInt32 m_nUsersPixelBLOBFormat; + XnUInt32 m_nUsersPixelBLOBSize; + + XnUInt16 m_nUsersPixelXRes; + XnUInt16 m_nUsersPixelYRes; +} XnLinkUserFrameHeader; + +typedef struct XnLinkUserFrame +{ + XnLinkUserFrameHeader m_header; + + // Using 2 unfixed sized fields, Commented out to avoid direct usage + //XnUChar m_UsersPixelsBLOB[1]; + //XnLinkUserData m_aUsers[1]; +} XnLinkUserFrame; + +typedef struct XnLinkEESectionHeader +{ + XnUInt32 m_nMagic; //Should be "LOAD" + XnUInt32 m_nSize; +} XnLinkEESectionHeader; + +typedef struct XnLinkIDSetHeader +{ + XnUInt16 m_nFormat; //Values come from XnLinkIDSetFormat. Currently must be XN_LINK_IDS_LIST_FORMAT_BITSET. + XnUInt16 m_nNumGroups; +} XnLinkIDSetHeader; + +typedef struct XnLinkIDSetGroupHeader +{ + XnUInt8 m_nGroupID; + XnUInt8 m_nSize; +} XnLinkIDSetGroupHeader; + +typedef struct XnLinkIDSetGroup +{ + XnLinkIDSetGroupHeader m_header; + XnUInt8 m_idsBitmap[1]; //Contains a bit of 1 for every id in the set, 0 for id not in the set +} XnLinkIDSetGroup; + +typedef struct XnLinkCropping +{ + /** TRUE if cropping is turned on, FALSE otherwise. */ + XnUInt8 m_bEnabled; + + XnUInt8 m_nReserved1; + XnUInt8 m_nReserved2; + XnUInt8 m_nReserved3; + + /** Offset in the X-axis, in pixels. */ + XnUInt16 m_nXOffset; + /** Offset in the Y-axis, in pixels. */ + XnUInt16 m_nYOffset; + /** Number of pixels in the X-axis. */ + XnUInt16 m_nXSize; + /** Number of pixels in the Y-axis. */ + XnUInt16 m_nYSize; +} XnLinkCropping; + +typedef struct XnLinkStreamIDsList +{ + XnUInt16 m_nNumStreamIDs; + XnUInt16 m_anStreamIDs[1]; +} XnLinkStreamIDsList; + +typedef struct XnLinkStreamIDsList XnLinkFrameSyncStreamIDs; + +typedef struct XnLinkPropValHeader +{ + XnUInt16 m_nPropType; //Values come from XnLinkPropType + XnUInt16 m_nPropID; //Values come from XnLinkInternalPropID + XnUInt32 m_nValueSize; +} XnLinkPropValHeader; + +typedef struct XnLinkPropVal +{ + XnLinkPropValHeader m_header; + XnUInt8 m_value[1]; +} XnLinkPropVal; + +typedef struct XnLinkPropSet +{ + XnUInt32 m_nNumProps; + //Followed by a number of XnLinkPropVal's + XnUInt8 m_aData[1]; +} XnLinkPropSet; + +typedef struct XnLinkBistTest +{ + XnUInt32 m_nID; + XnChar m_strName[XN_LINK_MAX_BIST_NAME_LENGTH]; +} XnLinkBistTest; + +typedef struct XnLinkSupportedBistTests +{ + XnUInt32 m_nCount; + XnLinkBistTest m_aTests[1]; +} XnLinkSupportedBistTests; + +typedef struct XnLinkUploadFileHeader +{ + XnBool m_bOverrideFactorySettings; +} XnLinkUploadFileHeader; + +typedef struct XnLinkLeanVersion +{ + XnUInt8 m_nMajor; + XnUInt8 m_nMinor; + XnUInt16 m_nReserved; +} XnLinkLeanVersion; + +typedef struct XnLinkDetailedVersion +{ + XnUInt8 m_nMajor; + XnUInt8 m_nMinor; + XnUInt16 m_nMaintenance; + XnUInt32 m_nBuild; + XnChar m_strModifier[XN_LINK_MAX_VERSION_MODIFIER_LENGTH]; +} XnLinkDetailedVersion; + +typedef struct XnLinkFileVersion +{ + XnUInt8 m_nMajor; + XnUInt8 m_nMinor; + XnUInt8 m_nMaintenance; + XnUInt8 m_nBuild; +} XnLinkFileVersion; + +typedef struct XnLinkFileEntry +{ + XnChar m_strName[XN_LINK_MAX_FILE_NAME_LENGTH]; + XnLinkFileVersion m_nVersion; + XnUInt32 m_nAddress; + XnUInt32 m_nSize; + XnUInt16 m_nCRC; + XnUInt16 m_nZone; + XnUInt8 m_nFlags; // bitmap of values from XnLinkFileFlags + XnUInt8 m_nReserved1; + XnUInt8 m_nReserved2; + XnUInt8 m_nReserved3; +} XnLinkFileEntry; + +typedef struct XnLinkComponentVersion +{ + XnChar m_strName[XN_LINK_MAX_COMPONENT_NAME_LENGTH]; + XnChar m_strVersion[XN_LINK_MAX_VERSION_LENGTH]; +} XnLinkComponentVersion; + +typedef struct XnLinkComponentVersionsList +{ + XnUInt32 m_nCount; + XnLinkComponentVersion m_components[1]; +} XnLinkComponentVersionsList; + + +typedef struct XnLinkAccCurentParam +{ + XnFloat m_nTemperature; + XnUInt32 m_nLutTabLine; + XnUInt16 m_nValueDC; + XnUInt16 m_nValueDac; + XnUInt16 m_nVoltage1; + XnUInt16 m_nVoltage2; +} XnLinkAccCurentParam; + +typedef struct XnLinkDCParam +{ + XnUInt32 m_nDCvalue; +} XnLinkDCParam ; + +typedef struct XnLinkCameraIntrinsics +{ + XnUInt16 m_nOpticalCenterX; + XnUInt16 m_nOpticalCenterY; + XnFloat m_fEffectiveFocalLengthInPixels; +} XnLinkCameraIntrinsics; + +typedef struct XnLinkI2CDevice +{ + XnUInt32 m_nID; + XnChar m_strName[XN_LINK_MAX_I2C_DEVICE_NAME_LENGTH]; +} XnLinkI2CDevice; + +typedef struct XnLinkSupportedI2CDevices +{ + XnUInt32 m_nCount; + XnLinkI2CDevice m_aI2CDevices[1]; +} XnLinkSupportedI2CDevices; + +typedef struct XnLinkLogFile +{ + XnUInt8 m_nID; + XnChar m_strName[XN_LINK_MAX_LOG_FILE_NAME_LENGTH]; +} XnLinkLogFile; + +typedef struct XnLinkSupportedLogFiles +{ + XnUInt32 m_nCount; + XnLinkLogFile m_aLogFiles[1]; +} XnLinkSupportedLogFiles; + +typedef struct XnLinkProjectorPulse +{ + XnUInt8 m_bEnabled; + XnUInt8 m_nReserved; + XnUInt16 m_nDelay; // Delay between frame start and the start of pulse, in milliseconds + XnUInt16 m_nWidth; // Pulse width, in milliseconds + XnUInt16 m_nFramesToSkip; // number of frames to skip between projector pulses, from pulse start to next pulse start. +} XnLinkProjectorPulse; + +//----------------------------------------------------------------------- +// Command Parameters +//----------------------------------------------------------------------- +typedef struct XnLinkDownloadFileParams +{ + XnUInt16 m_nZone; + XnUInt16 m_nReserved1; + XnChar m_strName[XN_LINK_MAX_FILE_NAME_LENGTH]; +} XnLinkDownloadFileParams; + +typedef struct XnLinkContinueReponseParams +{ + XnUInt16 m_nOriginalMsgType; +} XnLinkContinueReponseParams; + +typedef struct XnLinkWriteI2CParams +{ + XnUInt8 m_nDeviceID; + XnUInt8 m_nAddressSize; + XnUInt8 m_nValueSize; + XnUInt8 m_nReserved; + XnUInt32 m_nAddress; + XnUInt32 m_nValue; + XnUInt32 m_nMask; +} XnLinkWriteI2CParams; + +typedef struct XnLinkReadI2CParams +{ + XnUInt8 m_nDeviceID; + XnUInt8 m_nAddressSize; + XnUInt8 m_nValueSize; + XnUInt8 m_nReserved; + XnUInt32 m_nAddress; +} XnLinkReadI2CParams; + +typedef struct XnLinkWriteAHBParams +{ + XnUInt32 m_nAddress; + XnUInt32 m_nValue; + XnUInt8 m_nBitOffset; //Offset in bits of value to write within address + XnUInt8 m_nBitWidth; //Width in bits of value to write + XnUInt16 m_nReserved; +} XnLinkWriteAHBParams; + +typedef struct XnLinkReadAHBParams +{ + XnUInt32 m_nAddress; + XnUInt8 m_nBitOffset; //Offset in bits of value to read within address + XnUInt8 m_nBitWidth; //Width in bits of value to read + XnUInt16 m_nReserved; +} XnLinkReadAHBParams; + +typedef struct XnLinkStreamIDsList XnLinkStartStreamingMultiParams; +typedef struct XnLinkStreamIDsList XnLinkStopStreamingMultiParams; + +typedef struct XnLinkSetVideoModeParams +{ + XnLinkVideoMode m_videoMode; +} XnLinkSetVideoModeParams; + +//typedef struct XnLinkEnumerateStreamsParams +//{ +// XnUInt32 m_nStreamType; +//} XnLinkEnumerateStreamsParams; + +typedef struct XnLinkCreateStreamParams +{ + XnUInt32 m_nStreamType; + XnChar m_strCreationInfo[XN_LINK_MAX_CREATION_INFO_LENGTH]; +} XnLinkCreateStreamParams; + +typedef XnLinkPropVal XnLinkSetPropParams; + +typedef struct XnLinkGetPropParams +{ + XnUInt16 m_nPropType; //Values come from XnLinkPropType + XnUInt16 m_nPropID; //Values come from XnLinkInternalPropID +} XnLinkGetPropParams; + +typedef struct XnLinkSetMultiPropsParams +{ + XnUInt32 m_nNumProps; + //Followed by a number of XnLinkSetPropParams + XnUInt8 m_aData[1]; +} XnLinkSetMultiPropsParams; + +typedef struct XnLinkStartTrackingHandParams +{ + XnLinkPoint3D m_ptPosition; +} XnLinkStartTrackingHandParams; + +typedef struct XnLinkStopTrackingHandParams +{ + XnUInt32 m_nUserID; +} XnLinkStopTrackingHandParams; + +typedef struct XnLinkAddGestureParams +{ + XnUInt32 m_nGestureType; //Values come from XnLinkGestureType + XnLinkBoundingBox3D m_boundingBox; +} XnLinkAddGestureParams; + +typedef struct XnLinkRemoveGestureParams +{ + XnUInt32 m_nGestureType; //Values come from XnLinkGestureType +} XnLinkRemoveGestureParams; + +typedef struct XnLinkExecuteBistParams +{ + XnUInt32 m_nID; +} XnLinkExecuteBistParams; + +typedef struct XnLinkFormatZoneParams +{ + XnUInt32 m_nZone; //0 or 1 +} XnLinkFormatZoneParams; + +typedef struct XnLinkLogOpenCloseParams +{ + XnUInt8 m_nID; +} XnLinkLogOpenCloseParams; + +//----------------------------------------------------------------------- +// Command Response Structures +//----------------------------------------------------------------------- +typedef struct XnLinkResponseInfo +{ + XnUInt16 m_nResponseCode; + XnUInt16 m_nReserverd; +} XnLinkResponseInfo; + +typedef struct XnLinkResponseHeader +{ + XnLinkPacketHeader m_header; + XnLinkResponseInfo m_responseInfo; +} XnLinkResponseHeader; + +typedef struct XnLinkResponsePacket +{ + XnLinkResponseHeader m_responseHeader; + XnUInt8 m_data[1]; +} XnLinkResponsePacket; + +typedef struct XnLinkReadI2CResponse +{ + XnUInt32 m_nValue; +} XnLinkReadI2CResponse; + +typedef struct XnLinkReadAHBResponse +{ + XnUInt32 m_nValue; +} XnLinkReadAHBResponse; + +typedef XnLinkSupportedVideoModes XnLinkGetSupportedVideoModesResponse; + +typedef struct XnLinkGetVideoModeResponse +{ + XnLinkVideoMode m_videoMode; +} XnLinkGetVideoModeResponse; + +typedef struct XnLinkEnumerateStreamsResponse +{ + XnUInt32 m_nNumStreams; + XnLinkStreamInfo m_streamInfos[1]; +} XnLinkEnumerateStreamsResponse; + +typedef struct XnLinkCreateStreamResponse +{ + XnUInt16 m_nStreamID; + XnUInt16 m_nEndpointID; +} XnLinkCreateStreamResponse; + +typedef struct XnLinkPropVal XnLinkGetPropResponse; + +typedef struct XnLinkPropSet XnLinkGetAllPropsResponse; + +typedef struct XnLinkGetShiftToDepthConfigResponse +{ + XnLinkShiftToDepthConfig m_config; +} XnLinkGetShiftToDepthConfigResponse; + +typedef struct XnLinkEnumerateAvailableGesturesResponse +{ + XnUInt32 m_nGestures; + XnUInt32 m_nProgressSupported; + XnUInt32 m_nCurrentlyActive; +} XnLinkEnumerateAvailableGesturesResponse; + +typedef struct XnLinkEnumerateActiveGesturesResponse +{ + XnUInt32 m_nGestures; +} XnLinkEnumerateActiveGesturesResponse; + +typedef struct XnLinkSetSkeletonProfileParams +{ + XnUInt32 m_nProfile; +} XnLinkSetSkeletonProfileParams; + +typedef struct XnLinkSetSkeletonJointStateParams +{ + XnUInt16 m_nJoint; + XnUInt16 m_nState; +} XnLinkSetSkeletonJointStateParams; + +typedef struct XnLinkRequestSkeletonCalibrationParams +{ + XnUInt32 m_nUserID; + XnUInt32 m_bForce; +} XnLinkRequestSkeletonCalibrationParams; + +typedef struct XnLinkAbortSkeletonCalibrationParams +{ + XnUInt32 m_nUserID; +} XnLinkAbortSkeletonCalibrationParams; + +typedef struct XnLinkStartSkeletonTrackingParams +{ + XnUInt32 m_nUserID; +} XnLinkStartSkeletonTrackingParams; + +typedef struct XnLinkStopSkeletonTrackingParams +{ + XnUInt32 m_nUserID; +} XnLinkStopSkeletonTrackingParams; + +typedef struct XnLinkResetSkeletonTrackingParams +{ + XnUInt32 m_nUserID; +} XnLinkResetSkeletonTrackingParams; + +typedef struct XnLinkStartPoseDetectionParams +{ + XnUInt32 m_nUserID; + XnUInt32 m_nPose; +} XnLinkStartPoseDetectionParams; + +typedef struct XnLinkStopPoseDetectionParams +{ + XnUInt32 m_nUserID; +} XnLinkStopPoseDetectionParams; + +typedef struct XnLinkSaveSkeletonCalibrationDataParams +{ + XnUInt32 m_nUserID; + XnUInt32 m_nSlot; +} XnLinkSaveSkeletonCalibrationDataParams; + +typedef struct XnLinkLoadSkeletonCalibrationDataParams +{ + XnUInt32 m_nUserID; + XnUInt32 m_nSlot; +} XnLinkLoadSkeletonCalibrationDataParams; + +typedef struct XnLinkIsSkeletonCalibrationSlotTakenParams +{ + XnUInt32 m_nSlot; +} XnLinkIsSkeletonCalibrationSlotTakenParams; + +typedef struct XnLinkIsSkeletonCalibrationSlotTakenResponse +{ + XnUInt32 m_nTaken; +} XnLinkIsSkeletonCalibrationSlotTakenResponse; + +typedef struct XnLinkClearSkeletonCalibrationSlotParams +{ + XnUInt32 m_nSlot; +} XnLinkClearSkeletonCalibrationSlotParams; + +typedef struct XnLinkGetSkeletonCalibrationDataParams +{ + XnUInt32 m_nUserID; +} XnLinkGetSkeletonCalibrationDataParams; + +typedef struct XnLinkSetSkeletonCalibrationDataParamsHeader +{ + XnUInt32 m_nUserID; + XnUInt32 m_nDataSize; +} XnLinkSetSkeletonCalibrationDataParamsHeader; + +typedef struct XnLinkSetSkeletonCalibrationDataParams +{ + XnLinkSetSkeletonCalibrationDataParamsHeader m_header; + XnUInt8 m_aData[1]; +} XnLinkSetSkeletonCalibrationDataParams; + +typedef struct XnLinkSetLogMaskSeverityParams +{ + XnChar m_strMask[XN_LINK_MAX_LOG_MASK_LENGTH]; + XnUInt32 m_nMinSeverity; // values from XnLogSeverity +} XnLinkSetLogMaskSeverityParams; + +typedef struct XnLinkGetLogMaskSeverityParams +{ + XnChar m_strMask[XN_LINK_MAX_LOG_MASK_LENGTH]; +} XnLinkGetLogMaskSeverityParams; + +typedef struct XnLinkGetLogMaskSeverityResponse +{ + XnUInt32 m_nMinSeverity; // values from XnLogSeverity +} XnLinkGetLogMaskSeverityResponse; + +typedef struct XnLinkExecuteBistResponse // Entire data in this struct is system- and test-specific +{ + XnUInt32 m_nErrorCode; // 0 for success + XnUInt32 m_nExtraDataSize; + XnUChar m_ExtraData[1]; +} XnLinkExecuteBistResponse; + +typedef struct XnLinkGetFileListResponse +{ + XnUInt32 m_nCount; + XnLinkFileEntry m_aFileEntries[1]; +} XnLinkGetFileListResponse; + +typedef struct XnLinkBootStatus +{ + XnUInt8 m_nZone; //Values come from XnLinkBootZone + XnUInt8 m_nErrorCode; //Values come from XnLinkBootErrorCode + XnUInt16 m_nReserved; +} XnLinkBootStatus; + +//----------------------------------------------------------------------- +// Device Notifications +//----------------------------------------------------------------------- + +#if XN_PLATFORM != XN_PLATFORM_ARC +#pragma pack (pop) +#endif + +#endif // __XNLINKPROTO_H__ diff --git a/Source/Drivers/PSLink/XnPsVersion.h b/Source/Drivers/PSLink/XnPsVersion.h new file mode 100644 index 0000000..14c4a8b --- /dev/null +++ b/Source/Drivers/PSLink/XnPsVersion.h @@ -0,0 +1,36 @@ +#ifndef _XN_PS_VERSION_H_ +#define _XN_PS_VERSION_H_ + +//--------------------------------------------------------------------------- +// Includes +//--------------------------------------------------------------------------- +#include + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +/** Xiron major version. */ +#define XN_PS_MAJOR_VERSION 6 +/** Xiron minor version. */ +#define XN_PS_MINOR_VERSION 2 +/** Xiron maintenance version. */ +#define XN_PS_MAINTENANCE_VERSION 2 +/** Xiron build version. */ +#define XN_PS_BUILD_VERSION 10 + +/** Xiron version (in brief string format): "Major.Minor.Maintenance (Build)" */ +#define XN_PS_BRIEF_VERSION_STRING \ + XN_STRINGIFY(XN_PS_MAJOR_VERSION) "." \ + XN_STRINGIFY(XN_PS_MINOR_VERSION) "." \ + XN_STRINGIFY(XN_PS_MAINTENANCE_VERSION) \ + " (Build " XN_STRINGIFY(XN_PS_BUILD_VERSION) ")" + +/** Xiron version (in numeric format): (Xiron major version * 100000000 + Xiron minor version * 1000000 + Xiron maintenance version * 10000 + Xiron build version). */ +#define XN_PS_VERSION (XN_PS_MAJOR_VERSION*100000000 + XN_PS_MINOR_VERSION*1000000 + XN_PS_MAINTENANCE_VERSION*10000 + XN_PS_BUILD_VERSION) + +/** Xiron version (in string format): "Major.Minor.Maintenance.Build-Platform (MMM DD YYYY HH:MM:SS)". */ +#define XN_PS_VERSION_STRING \ + XN_PS_BRIEF_VERSION_STRING "-" \ + XN_PLATFORM_STRING " (" XN_TIMESTAMP ")" + +#endif //_XN_VERSION_H_ diff --git a/Source/Drivers/TestDevice/TestDevice.cpp b/Source/Drivers/TestDevice/TestDevice.cpp new file mode 100644 index 0000000..e1832bf --- /dev/null +++ b/Source/Drivers/TestDevice/TestDevice.cpp @@ -0,0 +1,420 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#include "Driver\OniDriverAPI.h" +#include "XnLib.h" +#include "XnHash.h" +#include "XnEvent.h" + +#define TEST_RESOLUTION_X 320 +#define TEST_RESOLUTION_Y 240 + +class TestStream : public oni::driver::StreamBase +{ +public: + TestStream() : oni::driver::StreamBase() + { + m_osEvent.Create(TRUE); + m_sendCount = 0; + } + + ~TestStream() + { + stop(); + } + + OniStatus start() + { + xnOSCreateThread(threadFunc, this, &m_threadHandle); + + return ONI_STATUS_OK; + } + + void stop() + { + m_running = false; + } + + virtual OniStatus SetVideoMode(OniVideoMode*) = 0; + virtual OniStatus GetVideoMode(OniVideoMode* pVideoMode) = 0; + + OniStatus getProperty(int propertyId, void* data, int* pDataSize) + { + if (propertyId == ONI_STREAM_PROPERTY_VIDEO_MODE) + { + if (*pDataSize != sizeof(OniVideoMode)) + { + printf("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniVideoMode)); + return ONI_STATUS_ERROR; + } + return GetVideoMode((OniVideoMode*)data); + } + + return ONI_STATUS_NOT_IMPLEMENTED; + } + + OniStatus setProperty(int propertyId, const void* data, int dataSize) + { + if (propertyId == ONI_STREAM_PROPERTY_VIDEO_MODE) + { + if (dataSize != sizeof(OniVideoMode)) + { + printf("Unexpected size: %d != %d\n", dataSize, sizeof(OniVideoMode)); + return ONI_STATUS_ERROR; + } + return SetVideoMode((OniVideoMode*)data); + } + else if (propertyId == 666) + { + if (dataSize != sizeof(int)) + { + printf("Unexpected size: %d != %d\n", dataSize, sizeof(int)); + return ONI_STATUS_ERROR; + } + + // Increment the send count. + m_cs.Lock(); + m_sendCount += *((int*)data); + m_cs.Unlock(); + + // Raise the OS event, to allow thread to start working. + m_osEvent.Set(); + } + + return ONI_STATUS_NOT_IMPLEMENTED; + } + + virtual int GetBytesPerPixel() = 0; + +protected: + + // Thread + static XN_THREAD_PROC threadFunc(XN_THREAD_PARAM pThreadParam) + { + TestStream* pStream = (TestStream*)pThreadParam; + pStream->m_running = true; + + while (pStream->m_running) + { + pStream->m_osEvent.Wait(XN_WAIT_INFINITE); + int count = 0; + do + { + // Get the current count. + pStream->m_cs.Lock(); + count = pStream->m_sendCount; + if (pStream->m_sendCount > 0) + { + pStream->m_sendCount--; + } + pStream->m_cs.Unlock(); + + // Send the frame. + if (count > 0) + { + OniFrame* pFrame = pStream->getServices().acquireFrame(); + pStream->BuildFrame(pFrame); + pStream->raiseNewFrame(pFrame); + pStream->getServices().releaseFrame(pFrame); + } + + } while (count > 0); + } + + XN_THREAD_PROC_RETURN(XN_STATUS_OK); + } + + virtual int BuildFrame(OniFrame* pFrame) = 0; + + int singleRes(int x, int y) {return y*TEST_RESOLUTION_X+x;} + + bool m_running; + int m_sendCount; + + XN_THREAD_HANDLE m_threadHandle; + + xnl::CriticalSection m_cs; + xnl::OSEvent m_osEvent; +}; + +class TestDepthStream : public TestStream +{ +public: + + TestDepthStream() : TestStream() + { + m_frameId = 1; + } + + OniStatus SetVideoMode(OniVideoMode*) {return ONI_STATUS_NOT_IMPLEMENTED;} + OniStatus GetVideoMode(OniVideoMode* pVideoMode) + { + pVideoMode->pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + pVideoMode->fps = 30; + pVideoMode->resolutionX = TEST_RESOLUTION_X; + pVideoMode->resolutionY = TEST_RESOLUTION_Y; + return ONI_STATUS_OK; + } + + virtual int GetBytesPerPixel() { return sizeof(OniDepthPixel); } + +private: + + virtual int BuildFrame(OniFrame* pFrame) + { + pFrame->frameIndex = m_frameId; + + pFrame->videoMode.pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + pFrame->videoMode.resolutionX = TEST_RESOLUTION_X; + pFrame->videoMode.resolutionY = TEST_RESOLUTION_Y; + pFrame->videoMode.fps = 30; + + pFrame->width = TEST_RESOLUTION_X; + pFrame->height = TEST_RESOLUTION_Y; + + pFrame->cropOriginX = pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + + pFrame->sensorType = ONI_SENSOR_DEPTH; + pFrame->stride = TEST_RESOLUTION_X*sizeof(OniDepthPixel); + pFrame->timestamp = m_frameId*33000; + m_frameId++; + return 1; + } + + int m_frameId; +}; + +class TestImageStream : public TestStream +{ +public: + TestImageStream() : TestStream() + { + m_frameId = 1; + } + + OniStatus SetVideoMode(OniVideoMode*) {return ONI_STATUS_NOT_IMPLEMENTED;} + OniStatus GetVideoMode(OniVideoMode* pVideoMode) + { + pVideoMode->pixelFormat = ONI_PIXEL_FORMAT_RGB888; + pVideoMode->fps = 30; + pVideoMode->resolutionX = TEST_RESOLUTION_X; + pVideoMode->resolutionY = TEST_RESOLUTION_Y; + return ONI_STATUS_OK; + } + + virtual int GetBytesPerPixel() { return sizeof(OniRGB888Pixel); } + +private: + + virtual int BuildFrame(OniFrame* pFrame) + { + pFrame->frameIndex = m_frameId; + + pFrame->videoMode.pixelFormat = ONI_PIXEL_FORMAT_RGB888; + pFrame->videoMode.resolutionX = TEST_RESOLUTION_X; + pFrame->videoMode.resolutionY = TEST_RESOLUTION_Y; + pFrame->videoMode.fps = 30; + + pFrame->width = TEST_RESOLUTION_X; + pFrame->height = TEST_RESOLUTION_Y; + + pFrame->cropOriginX = pFrame->cropOriginY = 0; + pFrame->croppingEnabled = FALSE; + + pFrame->sensorType = ONI_SENSOR_COLOR; + pFrame->stride = TEST_RESOLUTION_X*sizeof(OniRGB888Pixel); + pFrame->timestamp = m_frameId*33000; + m_frameId++; + return 1; + } + + int m_frameId; +}; + +class TestDevice : public oni::driver::DeviceBase +{ +public: + TestDevice(OniDeviceInfo* pInfo, oni::driver::DriverServices& driverServices) : m_pInfo(pInfo), m_driverServices(driverServices) + { + m_numSensors = 2; + + m_sensors[0].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, 1); + m_sensors[0].sensorType = ONI_SENSOR_DEPTH; + m_sensors[0].numSupportedVideoModes = 1; + m_sensors[0].pSupportedVideoModes[0].pixelFormat = ONI_PIXEL_FORMAT_DEPTH_1_MM; + m_sensors[0].pSupportedVideoModes[0].fps = 30; + m_sensors[0].pSupportedVideoModes[0].resolutionX = TEST_RESOLUTION_X; + m_sensors[0].pSupportedVideoModes[0].resolutionY = TEST_RESOLUTION_Y; + + m_sensors[1].pSupportedVideoModes = XN_NEW_ARR(OniVideoMode, 1); + m_sensors[1].sensorType = ONI_SENSOR_COLOR; + m_sensors[1].numSupportedVideoModes = 1; + m_sensors[1].pSupportedVideoModes[0].pixelFormat = ONI_PIXEL_FORMAT_RGB888; + m_sensors[1].pSupportedVideoModes[0].fps = 30; + m_sensors[1].pSupportedVideoModes[0].resolutionX = TEST_RESOLUTION_X; + m_sensors[1].pSupportedVideoModes[0].resolutionY = TEST_RESOLUTION_Y; + + } + OniDeviceInfo* GetInfo() + { + return m_pInfo; + } + + OniStatus getSensorInfoList(OniSensorInfo** pSensors, int* numSensors) + { + *numSensors = m_numSensors; + *pSensors = m_sensors; + + return ONI_STATUS_OK; + } + + oni::driver::StreamBase* createStream(OniSensorType sensorType) + { + if (sensorType == ONI_SENSOR_DEPTH) + { + TestDepthStream* pDepth = XN_NEW(TestDepthStream); + return pDepth; + } + if (sensorType == ONI_SENSOR_COLOR) + { + TestImageStream* pImage = XN_NEW(TestImageStream); + return pImage; + } + + m_driverServices.errorLoggerAppend("TestDevice: Can't create a stream of type %d", sensorType); + return NULL; + } + + void destroyStream(oni::driver::StreamBase* pStream) + { + XN_DELETE(pStream); + } + + OniStatus getProperty(int propertyId, void* data, int* pDataSize) + { + OniStatus rc = ONI_STATUS_OK; + + switch (propertyId) + { + case ONI_DEVICE_PROPERTY_DRIVER_VERSION: + { + if (*pDataSize == sizeof(OniVersion)) + { + OniVersion* version = (OniVersion*)data; + version->major = version->minor = version->maintenance = version->build = 2; + } + else + { + m_driverServices.errorLoggerAppend("Unexpected size: %d != %d\n", *pDataSize, sizeof(OniVersion)); + rc = ONI_STATUS_ERROR; + } + } + break; + default: + m_driverServices.errorLoggerAppend("Unknown property: %d\n", propertyId); + rc = ONI_STATUS_ERROR; + } + return rc; + } +private: + TestDevice(const TestDevice&); + void operator=(const TestDevice&); + + OniDeviceInfo* m_pInfo; + int m_numSensors; + OniSensorInfo m_sensors[10]; + oni::driver::DriverServices& m_driverServices; +}; + + +class TestDriver : public oni::driver::DriverBase +{ +public: + TestDriver(OniDriverServices* pDriverServices) : DriverBase(pDriverServices) + {} + + virtual oni::driver::DeviceBase* deviceOpen(const char* uri, const char* /*mode*/) + { + for (xnl::Hash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (xnOSStrCmp(iter->Key()->uri, uri) == 0) + { + // Found + if (iter->Value() != NULL) + { + // already using + return iter->Value(); + } + + TestDevice* pDevice = XN_NEW(TestDevice, iter->Key(), getServices()); + iter->Value() = pDevice; + return pDevice; + } + } + + getServices().errorLoggerAppend("Looking for '%s'", uri); + return NULL; + } + + virtual void deviceClose(oni::driver::DeviceBase* pDevice) + { + for (xnl::Hash::Iterator iter = m_devices.Begin(); iter != m_devices.End(); ++iter) + { + if (iter->Value() == pDevice) + { + iter->Value() = NULL; + XN_DELETE(pDevice); + return; + } + } + + // not our device?! + XN_ASSERT(FALSE); + } + + virtual OniStatus tryDevice(const char* uri) + { + if (xnOSStrCmp(uri, "Test")) + { + return ONI_STATUS_ERROR; + } + + + OniDeviceInfo* pInfo = XN_NEW(OniDeviceInfo); + xnOSStrCopy(pInfo->uri, uri, ONI_MAX_STR); + xnOSStrCopy(pInfo->vendor, "Test", ONI_MAX_STR); + m_devices[pInfo] = NULL; + + deviceConnected(pInfo); + + return ONI_STATUS_OK; + } + + void shutdown() {} + +protected: + + XN_THREAD_HANDLE m_threadHandle; + + xnl::Hash m_devices; +}; + +ONI_EXPORT_DRIVER(TestDriver); diff --git a/Source/Drivers/TestDevice/TestDevice.vcxproj b/Source/Drivers/TestDevice/TestDevice.vcxproj new file mode 100644 index 0000000..e20a074 --- /dev/null +++ b/Source/Drivers/TestDevice/TestDevice.vcxproj @@ -0,0 +1,179 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {31F0F25B-A84A-48AC-9716-5DF9137F3855} + TestDevice + + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\OpenNI2\Drivers\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + _WINDLL;%(PreprocessorDefinitions);TestDevice2_EXPORT + ProgramDatabase + Level4 + true + true + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + XnLib.lib;%(AdditionalDependencies) + true + + + + + Disabled + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + _WINDLL;%(PreprocessorDefinitions);TestDevice2_EXPORT + ProgramDatabase + Level4 + true + true + + + true + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + XnLib.lib;%(AdditionalDependencies) + true + + + + + Level4 + MaxSpeed + true + _MBCS;%(PreprocessorDefinitions);TestDevice2_EXPORT + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + + + Level4 + MaxSpeed + true + _MBCS;%(PreprocessorDefinitions);TestDevice2_EXPORT + ..\..\..\Include;..\..\..\ThirdParty\PSCommon\XnLib\Include + true + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + + + true + true + true + XnLib.lib;%(AdditionalDependencies) + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Resources/OpenNI.rc b/Source/Resources/OpenNI.rc new file mode 100644 index 0000000..c288d75 --- /dev/null +++ b/Source/Resources/OpenNI.rc @@ -0,0 +1,123 @@ +// Microsoft Visual C++ generated resource script. +// +#include "Resource-OpenNI.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Swedish resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_SVE) +#ifdef _WIN32 +LANGUAGE LANG_SWEDISH, SUBLANG_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "Resource-OpenNI.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""windows.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Swedish resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// Hebrew resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_HEB) +#ifdef _WIN32 +LANGUAGE LANG_HEBREW, SUBLANG_DEFAULT +#pragma code_page(1255) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_MAINICON ICON "mainicon.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ONI_VERSION_MAJOR,ONI_VERSION_MINOR,ONI_VERSION_MAINTENANCE,ONI_VERSION_BUILD + PRODUCTVERSION ONI_VERSION_MAJOR,ONI_VERSION_MINOR,ONI_VERSION_MAINTENANCE,ONI_VERSION_BUILD + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000904b0" + BEGIN + VALUE "CompanyName", "PrimeSense Ltd." + VALUE "FileDescription", "OpenNI" + VALUE "FileVersion", ONI_BRIEF_VERSION_STRING + VALUE "InternalName", "OpenNI" + VALUE "LegalCopyright", "Copyright (C) 2010" + VALUE "ProductName", "OpenNI" + VALUE "ProductVersion", ONI_BRIEF_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x9, 1200 + END +END + +#endif // Hebrew resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Source/Resources/Resource-OpenNI.h b/Source/Resources/Resource-OpenNI.h new file mode 100644 index 0000000..13c59a5 --- /dev/null +++ b/Source/Resources/Resource-OpenNI.h @@ -0,0 +1,36 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by OpenNI.rc +// +#define IDI_MAINICON 110 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 116 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1016 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Source/Resources/mainicon.ico b/Source/Resources/mainicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..cb534a375b0729cd3ee088b2b71a1c7538656f91 GIT binary patch literal 10534 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 literal 0 HcmV?d00001 diff --git a/Source/Tools/NiViewer/Audio.cpp b/Source/Tools/NiViewer/Audio.cpp new file mode 100644 index 0000000..f3cbe43 --- /dev/null +++ b/Source/Tools/NiViewer/Audio.cpp @@ -0,0 +1,249 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Includes +// -------------------------------- +#include "Audio.h" +#include "Device.h" +#include + +#if (XN_PLATFORM != XN_PLATFORM_WIN32) +void audioInit() {} +void audioPlay() {} +void audioShutdown(void) {} +#else // Win32 + +#pragma warning(push, 3) +#include +#pragma warning(pop) + +// -------------------------------- +// Defines +// -------------------------------- +#define NUMBER_OF_AUDIO_BUFFERS 20 +#define AUDIO_LATENCY_THRESHOLD 80000 + +#define AUDIO_SYNC_DUMP_MASK "RGBViewerAudioSync" + +// -------------------------------- +// Global Variables +// -------------------------------- +typedef struct AudioData +{ + HWAVEOUT hWaveOut; + WAVEHDR* pAudioBuffers; + int nBufferSize; + XnUInt64* pAudioTimestamps; + int nAudioNextBuffer; + bool bFlush; + int nFirstToCheck; + XnDumpFile* SyncDump; +} AudioData; + +AudioData g_AudioData; + +// -------------------------------- +// Code +// -------------------------------- +void audioPlay() +{ + if (g_AudioData.hWaveOut == NULL) // not initialized + return; + + const AudioMetaData* pAudioMD = getAudioMetaData(); + if (pAudioMD == NULL || pAudioMD->DataSize() == 0 || !pAudioMD->IsDataNew()) + return; + + if (g_AudioData.bFlush) + { + printf("Audio is falling behind. Flushing all queue.\n"); + xnDumpFileWriteString(g_AudioData.SyncDump, "Flushing queue...\n"); + + // mark not to check all dropped headers + g_AudioData.nFirstToCheck = g_AudioData.nAudioNextBuffer; + // flush all queued headers + waveOutReset(g_AudioData.hWaveOut); + + g_AudioData.bFlush = false; + return; + } + + int nBufferSize = pAudioMD->DataSize(); + + WAVEHDR* pHeader = &g_AudioData.pAudioBuffers[g_AudioData.nAudioNextBuffer]; + if ((pHeader->dwFlags & WHDR_DONE) == 0) + { + printf("No audio buffer is available!. Audio buffer will be lost!\n"); + return; + } + + // first unprepare this header + MMRESULT mmRes = waveOutUnprepareHeader(g_AudioData.hWaveOut, pHeader, sizeof(WAVEHDR)); + if (mmRes != MMSYSERR_NOERROR) + { + CHAR msg[250]; + waveOutGetErrorText(mmRes, msg, 250); + printf("Failed unpreparing header: %s\n", msg); + } + + int nMaxPlayedAudio = (int)(pAudioMD->SampleRate() / 1000.0 * pAudioMD->NumberOfChannels() * 2 * AUDIO_LATENCY_THRESHOLD); + if (nBufferSize > nMaxPlayedAudio) + { + printf("Dropping %d bytes of audio to keep synch.\n", nBufferSize - nMaxPlayedAudio); + nBufferSize = nMaxPlayedAudio; + } + + const XnUInt8* pData = pAudioMD->Data(); + + if (nBufferSize > g_AudioData.nBufferSize) + { + printf("Dropping %d bytes of audio to match buffer size.\n", nBufferSize - g_AudioData.nBufferSize); + pData += (nBufferSize - g_AudioData.nBufferSize); + nBufferSize = g_AudioData.nBufferSize; + } + + pHeader->dwFlags = 0; + xnOSMemCopy(pHeader->lpData, pData, nBufferSize); + pHeader->dwBufferLength = nBufferSize; + + // prepare header + mmRes = waveOutPrepareHeader(g_AudioData.hWaveOut, pHeader, sizeof(WAVEHDR)); + if (mmRes != MMSYSERR_NOERROR) + { + CHAR msg[250]; + waveOutGetErrorText(mmRes, msg, 250); + printf("Unable to prepare header: %s\n", msg); + return; + } + + // queue header + mmRes = waveOutWrite(g_AudioData.hWaveOut, pHeader, sizeof(WAVEHDR)); + if (mmRes != MMSYSERR_NOERROR) + { + CHAR msg[250]; + waveOutGetErrorText(mmRes, msg, 250); + printf("Unable to queue header: %s\n", msg); + return; + } + + // place end-time as a timestamp + g_AudioData.pAudioTimestamps[g_AudioData.nAudioNextBuffer] = (XnUInt64)(pAudioMD->Timestamp() + nBufferSize / (pAudioMD->BitsPerSample() / 8.0) / pAudioMD->NumberOfChannels() / (pAudioMD->SampleRate() / 1e6)); + + xnDumpFileWriteString(g_AudioData.SyncDump, "Queued index %d with timestamp %llu (%u bytes, %f ms, end timestamp: %llu)\n", g_AudioData.nAudioNextBuffer, pAudioMD->Timestamp(), nBufferSize, nBufferSize / 2.0 / pAudioMD->NumberOfChannels() / (pAudioMD->SampleRate() / 1e3), g_AudioData.pAudioTimestamps[g_AudioData.nAudioNextBuffer]); + + g_AudioData.nAudioNextBuffer = (g_AudioData.nAudioNextBuffer + 1) % NUMBER_OF_AUDIO_BUFFERS; +} + +void CALLBACK audioCallback(HWAVEOUT /*hwo*/, UINT uMsg, DWORD_PTR /*dwInstance*/, DWORD_PTR dwParam1, DWORD_PTR /*dwParam2*/) +{ + if (uMsg == WOM_DONE) + { + WAVEHDR* pHeader = (WAVEHDR*)dwParam1; + int nIndex = (int)pHeader->dwUser; + + xnDumpFileWriteString(g_AudioData.SyncDump, "Done playing index %d.", nIndex); + + if (g_AudioData.nFirstToCheck == -1 || g_AudioData.nFirstToCheck == nIndex) + { + g_AudioData.nFirstToCheck = -1; + + // get the timestamp of the packet just done playing + XnUInt64 nPlayedTimestamp = g_AudioData.pAudioTimestamps[nIndex]; + + // check how much time is still queued + XnUInt32 nLastQueuedIndex = (g_AudioData.nAudioNextBuffer + NUMBER_OF_AUDIO_BUFFERS - 1) % NUMBER_OF_AUDIO_BUFFERS; + XnUInt64 nLastQueuedTimestamp = g_AudioData.pAudioTimestamps[nLastQueuedIndex]; + + xnDumpFileWriteString(g_AudioData.SyncDump, " %f ms in queue.", (nLastQueuedTimestamp - nPlayedTimestamp) / 1e3); + + if (nLastQueuedTimestamp - nPlayedTimestamp > AUDIO_LATENCY_THRESHOLD) + { + g_AudioData.bFlush = true; + xnDumpFileWriteString(g_AudioData.SyncDump, " Will flush queue.\n"); + } + else + xnDumpFileWriteString(g_AudioData.SyncDump, "\n"); + } + else + xnDumpFileWriteString(g_AudioData.SyncDump, "\n"); + } +} + +void audioInit() +{ + g_AudioData.hWaveOut = NULL; + g_AudioData.bFlush = false; + g_AudioData.nFirstToCheck = -1; + g_AudioData.SyncDump = xnDumpFileOpen(AUDIO_SYNC_DUMP_MASK, "%s.txt", AUDIO_SYNC_DUMP_MASK);; + + // check if device audio is enabled + const AudioMetaData* pAudioMD = getAudioMetaData(); + if (pAudioMD == NULL) + return; + + // start audio out device + WAVEFORMATEX wf; + wf.wFormatTag = 0x0001; // PCM + wf.nChannels = pAudioMD->NumberOfChannels(); + wf.nSamplesPerSec = pAudioMD->SampleRate(); + wf.wBitsPerSample = pAudioMD->BitsPerSample(); + wf.nBlockAlign = wf.wBitsPerSample * wf.nChannels / 8; + wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; + MMRESULT mmRes = waveOutOpen(&g_AudioData.hWaveOut, WAVE_MAPPER, &wf, (DWORD_PTR)audioCallback, NULL, CALLBACK_FUNCTION); + if (mmRes != MMSYSERR_NOERROR) + { + printf("Warning: Failed opening wave out device. Audio will not be played!\n"); + g_AudioData.hWaveOut = NULL; + return; + } + + // create some wave headers for playing + g_AudioData.pAudioBuffers = new WAVEHDR[NUMBER_OF_AUDIO_BUFFERS]; + g_AudioData.pAudioTimestamps = new XnUInt64[NUMBER_OF_AUDIO_BUFFERS]; + xnOSMemSet(g_AudioData.pAudioBuffers, 0, sizeof(WAVEHDR)*NUMBER_OF_AUDIO_BUFFERS); + + // allocate max buffer for one second + g_AudioData.nBufferSize = wf.nAvgBytesPerSec; + + for (int i = 0; i < NUMBER_OF_AUDIO_BUFFERS; ++i) + { + g_AudioData.pAudioBuffers[i].lpData = new XnChar[g_AudioData.nBufferSize]; + g_AudioData.pAudioBuffers[i].dwUser = i; + g_AudioData.pAudioBuffers[i].dwFlags = WHDR_DONE; // mark this buffer as empty (already played) + } + + g_AudioData.nAudioNextBuffer = 0; +} + +void audioShutdown() +{ + if (g_AudioData.hWaveOut != NULL) + { + waveOutClose(g_AudioData.hWaveOut); + + for (int i = 0; i < NUMBER_OF_AUDIO_BUFFERS; ++i) + delete[] g_AudioData.pAudioBuffers[i].lpData; + + delete[] g_AudioData.pAudioBuffers; + } +} + +#endif // Win32 diff --git a/Source/Tools/NiViewer/Audio.h b/Source/Tools/NiViewer/Audio.h new file mode 100644 index 0000000..64cb0f0 --- /dev/null +++ b/Source/Tools/NiViewer/Audio.h @@ -0,0 +1,36 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __AUDIO_H__ +#define __AUDIO_H__ + +// -------------------------------- +// Includes +// -------------------------------- +#include + +// -------------------------------- +// Function Declarations +// -------------------------------- +void audioInit(); +void audioPlay(); +void audioShutdown(void); + +#endif //__AUDIO_H__ \ No newline at end of file diff --git a/Source/Tools/NiViewer/Capture.cpp b/Source/Tools/NiViewer/Capture.cpp new file mode 100644 index 0000000..ac88e7d --- /dev/null +++ b/Source/Tools/NiViewer/Capture.cpp @@ -0,0 +1,466 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Includes +// -------------------------------- +#include +#include "Capture.h" +#include "Device.h" +#include "Draw.h" + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) +#include +#endif + +// -------------------------------- +// Defines +// -------------------------------- +#define CAPTURED_FRAMES_DIR_NAME "CapturedFrames" + +// -------------------------------- +// Types +// -------------------------------- +typedef enum +{ + NOT_CAPTURING, + SHOULD_CAPTURE, + CAPTURING, +} CapturingState; + +typedef enum +{ + CAPTURE_DEPTH_STREAM, + CAPTURE_COLOR_STREAM, + CAPTURE_IR_STREAM, + CAPTURE_STREAM_COUNT +} CaptureSourceType; + +typedef enum +{ + STREAM_CAPTURE_LOSSLESS = FALSE, + STREAM_CAPTURE_LOSSY = TRUE, + STREAM_DONT_CAPTURE, +} StreamCaptureType; + +typedef struct StreamCapturingData +{ + StreamCaptureType captureType; + const char* name; + bool bRecording; + openni::VideoFrameRef& (*getFrameFunc)(); + openni::VideoStream& (*getStream)(); + bool (*isStreamOn)(); + int startFrame; +} StreamCapturingData; + +typedef struct CapturingData +{ + StreamCapturingData streams[CAPTURE_STREAM_COUNT]; + openni::Recorder recorder; + char csFileName[256]; + int nStartOn; // time to start, in seconds + bool bSkipFirstFrame; + CapturingState State; + int nCapturedFrameUniqueID; + char csDisplayMessage[500]; +} CapturingData; + +// -------------------------------- +// Static Global Variables +// -------------------------------- +CapturingData g_Capture; + +DeviceParameter g_DepthCapturing; +DeviceParameter g_ColorCapturing; +DeviceParameter g_IRCapturing; + +// -------------------------------- +// Code +// -------------------------------- +void captureInit() +{ + // Depth Formats + int nIndex = 0; + + g_DepthCapturing.pValues[nIndex] = STREAM_CAPTURE_LOSSLESS; + g_DepthCapturing.pValueToName[nIndex] = "Lossless"; + nIndex++; + + g_DepthCapturing.pValues[nIndex] = STREAM_DONT_CAPTURE; + g_DepthCapturing.pValueToName[nIndex] = "Don't Capture"; + nIndex++; + + g_DepthCapturing.nValuesCount = nIndex; + + // Color Formats + nIndex = 0; + + g_ColorCapturing.pValues[nIndex] = STREAM_CAPTURE_LOSSLESS; + g_ColorCapturing.pValueToName[nIndex] = "Lossless"; + nIndex++; + + g_ColorCapturing.pValues[nIndex] = STREAM_CAPTURE_LOSSY; + g_ColorCapturing.pValueToName[nIndex] = "Lossy"; + nIndex++; + + g_ColorCapturing.pValues[nIndex] = STREAM_DONT_CAPTURE; + g_ColorCapturing.pValueToName[nIndex] = "Don't Capture"; + nIndex++; + + g_ColorCapturing.nValuesCount = nIndex; + + // IR Formats + nIndex = 0; + + g_IRCapturing.pValues[nIndex] = STREAM_CAPTURE_LOSSLESS; + g_IRCapturing.pValueToName[nIndex] = "Lossless"; + nIndex++; + + g_IRCapturing.pValues[nIndex] = STREAM_DONT_CAPTURE; + g_IRCapturing.pValueToName[nIndex] = "Don't Capture"; + nIndex++; + + g_IRCapturing.nValuesCount = nIndex; + + // Init + g_Capture.csFileName[0] = 0; + g_Capture.State = NOT_CAPTURING; + g_Capture.nCapturedFrameUniqueID = 0; + g_Capture.csDisplayMessage[0] = '\0'; + g_Capture.bSkipFirstFrame = false; + + g_Capture.streams[CAPTURE_DEPTH_STREAM].captureType = STREAM_CAPTURE_LOSSLESS; + g_Capture.streams[CAPTURE_DEPTH_STREAM].name = "Depth"; + g_Capture.streams[CAPTURE_DEPTH_STREAM].getFrameFunc = getDepthFrame; + g_Capture.streams[CAPTURE_DEPTH_STREAM].getStream = getDepthStream; + g_Capture.streams[CAPTURE_DEPTH_STREAM].isStreamOn = isDepthOn; + g_Capture.streams[CAPTURE_COLOR_STREAM].captureType = STREAM_CAPTURE_LOSSY; + g_Capture.streams[CAPTURE_COLOR_STREAM].name = "Color"; + g_Capture.streams[CAPTURE_COLOR_STREAM].getFrameFunc = getColorFrame; + g_Capture.streams[CAPTURE_COLOR_STREAM].getStream = getColorStream; + g_Capture.streams[CAPTURE_COLOR_STREAM].isStreamOn = isColorOn; + g_Capture.streams[CAPTURE_IR_STREAM].captureType = STREAM_CAPTURE_LOSSLESS; + g_Capture.streams[CAPTURE_IR_STREAM].name = "IR"; + g_Capture.streams[CAPTURE_IR_STREAM].getFrameFunc = getIRFrame; + g_Capture.streams[CAPTURE_IR_STREAM].getStream = getIRStream; + g_Capture.streams[CAPTURE_IR_STREAM].isStreamOn = isIROn; +} + +bool isCapturing() +{ + return (g_Capture.State != NOT_CAPTURING); +} + +void captureBrowse(int) +{ +#if (ONI_PLATFORM == ONI_PLATFORM_WIN32) + OPENFILENAME ofn = { 0 }; + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = TEXT("Oni Files (*.oni)\0*.oni\0"); + ofn.nFilterIndex = 1; + ofn.lpstrFile = g_Capture.csFileName; + ofn.nMaxFile = sizeof(g_Capture.csFileName); + ofn.lpstrTitle = TEXT("Capture to..."); + ofn.lpstrDefExt = TEXT("oni"); + ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR; + BOOL gotFileName = GetSaveFileName(&ofn); + + if (gotFileName) + { + if (g_Capture.csFileName[0] != 0) + { + if (strstr(g_Capture.csFileName, ".oni") == NULL) + { + strcat(g_Capture.csFileName, ".oni"); + } + } + } +#else + // Set capture file to defaults. + strcpy(g_Capture.csFileName, "./Captured.oni"); +#endif // ONI_PLATFORM_WIN32 + + // as we waited for user input, it's probably better to discard first frame (especially if an accumulating + // stream is on, like audio). + g_Capture.bSkipFirstFrame = true; +} + +void captureStart(int nDelay) +{ + captureBrowse(0); + + // On some platforms a user can cancel capturing. Whenever he cancels + // capturing, the gs_filePath[0] remains empty. + if ('\0' == g_Capture.csFileName[0]) + { + return; + } + + openni::Status rc = g_Capture.recorder.create(g_Capture.csFileName); + if (rc != openni::STATUS_OK) + { + displayError("Failed to create recorder!"); + return; + } + + XnUInt64 nNow; + xnOSGetTimeStamp(&nNow); + nNow /= 1000; + + g_Capture.nStartOn = (XnUInt32)nNow + nDelay; + g_Capture.State = SHOULD_CAPTURE; +} + +void captureRestart(int) +{ + captureStop(0); + captureStart(0); +} + +void captureStop(int) +{ + if (g_Capture.recorder.isValid()) + { + g_Capture.recorder.destroy(); + g_Capture.State = NOT_CAPTURING; + } +} + +#define START_CAPTURE_CHECK_RC(rc, what) \ + if (nRetVal != XN_STATUS_OK) \ + { \ + displayError("Failed to %s: %s\n", what, openni::OpenNI::getExtendedError()); \ + g_Capture.recorder.destroy(); \ + g_Capture.State = NOT_CAPTURING; \ + return; \ + } + +void captureRun() +{ + XnStatus nRetVal = XN_STATUS_OK; + + if (g_Capture.State != SHOULD_CAPTURE) + { + return; + } + + XnUInt64 nNow; + xnOSGetTimeStamp(&nNow); + nNow /= 1000; + + // check if time has arrived + if ((XnInt64)nNow >= g_Capture.nStartOn) + { + // check if we need to discard first frame + if (g_Capture.bSkipFirstFrame) + { + g_Capture.bSkipFirstFrame = false; + } + else + { + // start recording + for (int i = 0; i < CAPTURE_STREAM_COUNT; ++i) + { + g_Capture.streams[i].bRecording = false; + + if (g_Capture.streams[i].isStreamOn() && g_Capture.streams[i].captureType != STREAM_DONT_CAPTURE) + { + nRetVal = g_Capture.recorder.attach(g_Capture.streams[i].getStream(), g_Capture.streams[i].captureType == STREAM_CAPTURE_LOSSY); + START_CAPTURE_CHECK_RC(nRetVal, "add stream"); + g_Capture.streams[i].bRecording = TRUE; + g_Capture.streams[i].startFrame = g_Capture.streams[i].getFrameFunc().getFrameIndex(); + } + } + + nRetVal = g_Capture.recorder.start(); + START_CAPTURE_CHECK_RC(nRetVal, "start recording"); + g_Capture.State = CAPTURING; + } + } +} + +void captureSetDepthFormat(int format) +{ + g_Capture.streams[CAPTURE_DEPTH_STREAM].captureType = (StreamCaptureType)format; +} + +void captureSetColorFormat(int format) +{ + g_Capture.streams[CAPTURE_COLOR_STREAM].captureType = (StreamCaptureType)format; +} + +void captureSetIRFormat(int format) +{ + g_Capture.streams[CAPTURE_IR_STREAM].captureType = (StreamCaptureType)format; +} + +void getCaptureMessage(char* pMessage) +{ + switch (g_Capture.State) + { + case SHOULD_CAPTURE: + { + XnUInt64 nNow; + xnOSGetTimeStamp(&nNow); + nNow /= 1000; + sprintf(pMessage, "Capturing will start in %u seconds...", g_Capture.nStartOn - (XnUInt32)nNow); + } + break; + case CAPTURING: + { + int nChars = sprintf(pMessage, "* Recording! Press any key or use menu to stop *\nRecorded Frames: "); + for (int i = 0; i < CAPTURE_STREAM_COUNT; ++i) + { + if (g_Capture.streams[i].bRecording) + { + nChars += sprintf(pMessage + nChars, "%s-%d ", g_Capture.streams[i].name, g_Capture.streams[i].getFrameFunc().getFrameIndex() - g_Capture.streams[i].startFrame); + } + } + } + break; + default: + pMessage[0] = 0; + } +} + +void getColorFileName(int num, char* csName) +{ + sprintf(csName, "%s/Color_%d.raw", CAPTURED_FRAMES_DIR_NAME, num); +} + +void getDepthFileName(int num, char* csName) +{ + sprintf(csName, "%s/Depth_%d.raw", CAPTURED_FRAMES_DIR_NAME, num); +} + +void getIRFileName(int num, char* csName) +{ + sprintf(csName, "%s/IR_%d.raw", CAPTURED_FRAMES_DIR_NAME, num); +} + +int findUniqueFileName() +{ + xnOSCreateDirectory(CAPTURED_FRAMES_DIR_NAME); + + int num = g_Capture.nCapturedFrameUniqueID; + + XnBool bExist = FALSE; + XnStatus nRetVal = XN_STATUS_OK; + XnChar csColorFileName[XN_FILE_MAX_PATH]; + XnChar csDepthFileName[XN_FILE_MAX_PATH]; + XnChar csIRFileName[XN_FILE_MAX_PATH]; + + for (;;) + { + // check color + getColorFileName(num, csColorFileName); + + nRetVal = xnOSDoesFileExist(csColorFileName, &bExist); + if (nRetVal != XN_STATUS_OK) + break; + + if (!bExist) + { + // check depth + getDepthFileName(num, csDepthFileName); + + nRetVal = xnOSDoesFileExist(csDepthFileName, &bExist); + if (nRetVal != XN_STATUS_OK || !bExist) + break; + } + + if (!bExist) + { + // check IR + getIRFileName(num, csIRFileName); + + nRetVal = xnOSDoesFileExist(csIRFileName, &bExist); + if (nRetVal != XN_STATUS_OK || !bExist) + break; + } + + ++num; + } + + return num; +} + +void captureSingleFrame(int) +{ + int num = findUniqueFileName(); + + XnChar csColorFileName[XN_FILE_MAX_PATH]; + XnChar csDepthFileName[XN_FILE_MAX_PATH]; + XnChar csIRFileName[XN_FILE_MAX_PATH]; + getColorFileName(num, csColorFileName); + getDepthFileName(num, csDepthFileName); + getIRFileName(num, csIRFileName); + + openni::VideoFrameRef& colorFrame = getColorFrame(); + if (colorFrame.isValid()) + { + xnOSSaveFile(csColorFileName, colorFrame.getData(), colorFrame.getDataSize()); + } + + openni::VideoFrameRef& depthFrame = getDepthFrame(); + if (depthFrame.isValid()) + { + xnOSSaveFile(csDepthFileName, depthFrame.getData(), depthFrame.getDataSize()); + } + + openni::VideoFrameRef& irFrame = getIRFrame(); + if (irFrame.isValid()) + { + xnOSSaveFile(csIRFileName, irFrame.getData(), irFrame.getDataSize()); + } + + g_Capture.nCapturedFrameUniqueID = num + 1; + + displayMessage("Frames saved with ID %d", num); +} + +const char* getCaptureTypeName(StreamCaptureType type) +{ + switch (type) + { + case STREAM_CAPTURE_LOSSLESS: return "Lossless"; + case STREAM_CAPTURE_LOSSY: return "Lossy"; + case STREAM_DONT_CAPTURE: return "Don't Capture"; + default: + XN_ASSERT(FALSE); + return ""; + } +} + +const char* captureGetDepthFormatName() +{ + return getCaptureTypeName(g_Capture.streams[CAPTURE_DEPTH_STREAM].captureType); +} + +const char* captureGetColorFormatName() +{ + return getCaptureTypeName(g_Capture.streams[CAPTURE_COLOR_STREAM].captureType); +} + +const char* captureGetIRFormatName() +{ + return getCaptureTypeName(g_Capture.streams[CAPTURE_IR_STREAM].captureType); +} diff --git a/Source/Tools/NiViewer/Capture.h b/Source/Tools/NiViewer/Capture.h new file mode 100644 index 0000000..632549c --- /dev/null +++ b/Source/Tools/NiViewer/Capture.h @@ -0,0 +1,58 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __CAPTURE_H__ +#define __CAPTURE_H__ + +// -------------------------------- +// Includes +// -------------------------------- +#include "Device.h" + +// -------------------------------- +// Global Variables +// -------------------------------- +extern DeviceParameter g_DepthCapturing; +extern DeviceParameter g_ColorCapturing; +extern DeviceParameter g_IRCapturing; + +// -------------------------------- +// Function Declarations +// -------------------------------- +void captureInit(); +void captureBrowse(int); +void captureStart(int nDelay); +void captureRestart(int); +void captureStop(int); +bool isCapturing(); + +void captureSetDepthFormat(int format); +void captureSetColorFormat(int format); +void captureSetIRFormat(int format); +const char* captureGetDepthFormatName(); +const char* captureGetColorFormatName(); +const char* captureGetIRFormatName(); + +void captureRun(); +void captureSingleFrame(int); + +void getCaptureMessage(char* pMessage); + +#endif //__CAPTURE_H__ \ No newline at end of file diff --git a/Source/Tools/NiViewer/Device.cpp b/Source/Tools/NiViewer/Device.cpp new file mode 100644 index 0000000..28cbfee --- /dev/null +++ b/Source/Tools/NiViewer/Device.cpp @@ -0,0 +1,893 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Includes +// -------------------------------- +#include "OpenNI.h" +#include "Device.h" +#include "Draw.h" +#include +#include +#include + +// -------------------------------- +// Defines +// -------------------------------- +#define MAX_STRINGS 20 + +// -------------------------------- +// Global Variables +// -------------------------------- + +DeviceParameter g_Registration; +DeviceParameter g_Resolution; +bool g_bIsDepthOn = false; +bool g_bIsColorOn = false; +bool g_bIsIROn = false; + +openni::Device g_device; +openni::PlaybackControl* g_pPlaybackControl; + +openni::VideoStream g_depthStream; +openni::VideoStream g_colorStream; +openni::VideoStream g_irStream; + +openni::VideoFrameRef g_depthFrame; +openni::VideoFrameRef g_colorFrame; +openni::VideoFrameRef g_irFrame; + +const openni::SensorInfo* g_depthSensorInfo = NULL; +const openni::SensorInfo* g_colorSensorInfo = NULL; +const openni::SensorInfo* g_irSensorInfo = NULL; + +// -------------------------------- +// Code +// -------------------------------- +void initConstants() +{ +// // Primary Streams + int nIndex = 0; + + // Registration + nIndex = 0; + + g_Registration.pValues[nIndex++] = openni::IMAGE_REGISTRATION_OFF; + g_Registration.pValueToName[FALSE] = "Off"; + + g_Registration.pValues[nIndex++] = openni::IMAGE_REGISTRATION_DEPTH_TO_COLOR; + g_Registration.pValueToName[TRUE] = "Depth -> Image"; + + g_Registration.nValuesCount = nIndex; +} + +const char* getFormatName(openni::PixelFormat format) +{ + switch (format) + { + case openni::PIXEL_FORMAT_DEPTH_1_MM: + return "1 mm"; + case openni::PIXEL_FORMAT_DEPTH_100_UM: + return "100 um"; + case openni::PIXEL_FORMAT_SHIFT_9_2: + return "Shifts 9.2"; + case openni::PIXEL_FORMAT_SHIFT_9_3: + return "Shifts 9.3"; + case openni::PIXEL_FORMAT_RGB888: + return "RGB 888"; + case openni::PIXEL_FORMAT_YUV422: + return "YUV 422"; + case openni::PIXEL_FORMAT_YUYV: + return "YUYV"; + case openni::PIXEL_FORMAT_GRAY8: + return "Grayscale 8-bit"; + case openni::PIXEL_FORMAT_GRAY16: + return "Grayscale 16-bit"; + case openni::PIXEL_FORMAT_JPEG: + return "JPEG"; + default: + return "Unknown"; + } +} + +int openStream(openni::Device& device, const char* name, openni::SensorType sensorType, SensorOpenType openType, openni::VideoStream& stream, const openni::SensorInfo** ppSensorInfo, bool* pbIsStreamOn) +{ + *ppSensorInfo = device.getSensorInfo(sensorType); + *pbIsStreamOn = false; + + if (openType == SENSOR_OFF) + { + return 0; + } + + if (*ppSensorInfo == NULL) + { + if (openType == SENSOR_ON) + { + printf("No %s sensor available\n", name); + return -1; + } + else + { + return 0; + } + } + + openni::Status nRetVal = stream.create(device, sensorType); + if (nRetVal != openni::STATUS_OK) + { + if (openType == SENSOR_ON) + { + printf("Failed to create %s stream:\n%s\n", openni::OpenNI::getExtendedError(), name); + return -2; + } + else + { + return 0; + } + } + + nRetVal = stream.start(); + if (nRetVal != openni::STATUS_OK) + { + stream.destroy(); + + if (openType == SENSOR_ON) + { + printf("Failed to start depth stream:\n%s\n", openni::OpenNI::getExtendedError()); + return -3; + } + else + { + return 0; + } + } + + *pbIsStreamOn = true; + + return 0; +} + +int openCommon(openni::Device& device, DeviceConfig config) +{ + g_pPlaybackControl = g_device.getPlaybackControl(); + + int ret; + + ret = openStream(device, "depth", openni::SENSOR_DEPTH, config.openDepth, g_depthStream, &g_depthSensorInfo, &g_bIsDepthOn); + if (ret != 0) + { + return ret; + } + + ret = openStream(device, "color", openni::SENSOR_COLOR, config.openColor, g_colorStream, &g_colorSensorInfo, &g_bIsColorOn); + if (ret != 0) + { + return ret; + } + + ret = openStream(device, "IR", openni::SENSOR_IR, config.openIR, g_irStream, &g_irSensorInfo, &g_bIsIROn); + if (ret != 0) + { + return ret; + } + + initConstants(); + + readFrame(); + + return 0; +} + +class OpenNIDeviceListener : public openni::OpenNI::DeviceStateChangedListener, + public openni::OpenNI::DeviceDisconnectedListener +{ +public: + virtual void onDeviceStateChanged(const openni::DeviceInfo* pInfo, openni::DeviceState errorState) + { + if (strcmp(pInfo->getUri(), g_device.getDeviceInfo().getUri()) == 0) + { + if (errorState != 0) + { + setErrorState("Device is in error state! (error %d)", errorState); + } + else + { + setErrorState(""); + } + } + } + virtual void onDeviceDisconnected(const openni::DeviceInfo* pInfo) + { + if (strcmp(pInfo->getUri(), g_device.getDeviceInfo().getUri()) == 0) + { + setErrorState("Device disconnected!"); + } + } +}; + +openni::Status openDevice(const char* uri, DeviceConfig config) +{ + openni::Status nRetVal = openni::OpenNI::initialize(); + if (nRetVal != openni::STATUS_OK) + { + return nRetVal; + } + + // Register to OpenNI events. + static OpenNIDeviceListener deviceListener; + + openni::OpenNI::addDeviceDisconnectedListener(&deviceListener); + openni::OpenNI::addDeviceStateChangedListener(&deviceListener); + + // Open the requested device. + nRetVal = g_device.open(uri); + if (nRetVal != openni::STATUS_OK) + { + return nRetVal; + } + + if (0 != openCommon(g_device, config)) + { + return openni::STATUS_ERROR; + } + + return openni::STATUS_OK; +} + +openni::Status openDeviceFromList(DeviceConfig config) +{ + openni::Status rc = openni::OpenNI::initialize(); + if (rc != openni::STATUS_OK) + { + return rc; + } + + openni::Array deviceList; + openni::OpenNI::enumerateDevices(&deviceList); + + for (int i = 0; i < deviceList.getSize(); ++i) + { + printf("[%d] %s [%s] (%s)\n", i+1, deviceList[i].getName(), deviceList[i].getVendor(), deviceList[i].getUri()); + } + + printf("\n"); + int chosen = 1; + + do + { + printf("Choose device to open (1) [0 to exit]: "); + + int rc = scanf("%d", &chosen); + + if (rc <= 0 || chosen == 0) + { + return openni::STATUS_ERROR; + } + + } while (chosen < 1 || chosen > deviceList.getSize()); + + g_device.open(deviceList[chosen-1].getUri()); + + if (rc != openni::STATUS_OK) + { + return rc; + } + + if (0 != openCommon(g_device, config)) + { + return openni::STATUS_ERROR; + } + + return openni::STATUS_OK; +} + +void closeDevice() +{ + g_depthStream.stop(); + g_colorStream.stop(); + g_irStream.stop(); + + g_depthStream.destroy(); + g_colorStream.destroy(); + g_irStream.destroy(); + + g_device.close(); + + openni::OpenNI::shutdown(); +} + +void readFrame() +{ + openni::Status rc = openni::STATUS_OK; + + openni::VideoStream* streams[] = {&g_depthStream, &g_colorStream, &g_irStream}; + + int changedIndex = -1; + while (rc == openni::STATUS_OK) + { + rc = openni::OpenNI::waitForAnyStream(streams, 3, &changedIndex, 0); + if (rc == openni::STATUS_OK) + { + switch (changedIndex) + { + case 0: + g_depthStream.readFrame(&g_depthFrame); break; + case 1: + g_colorStream.readFrame(&g_colorFrame); break; + case 2: + g_irStream.readFrame(&g_irFrame); break; + default: + printf("Error in wait\n"); + } + } + } +} + +void changeRegistration(int value) +{ + openni::ImageRegistrationMode mode = (openni::ImageRegistrationMode)value; + if (!g_device.isValid() || !g_device.isImageRegistrationModeSupported(mode)) + { + return; + } + + g_device.setImageRegistrationMode(mode); +} + +void toggleMirror(int ) +{ + toggleDepthMirror(0); + toggleColorMirror(0); + toggleIRMirror(0); + + displayMessage ("Mirror: %s", g_depthStream.getMirroringEnabled()?"On":"Off"); +} + + +void toggleCloseRange(int ) +{ + bool bCloseRange; + g_depthStream.getProperty(XN_STREAM_PROPERTY_CLOSE_RANGE, &bCloseRange); + + bCloseRange = !bCloseRange; + + g_depthStream.setProperty(XN_STREAM_PROPERTY_CLOSE_RANGE, bCloseRange); + + displayMessage ("Close range: %s", bCloseRange?"On":"Off"); +} + +void toggleImageRegistration(int) +{ + openni::ImageRegistrationMode mode = g_device.getImageRegistrationMode(); + + openni::ImageRegistrationMode newMode = openni::IMAGE_REGISTRATION_OFF; + if (mode == openni::IMAGE_REGISTRATION_OFF) + { + newMode = openni::IMAGE_REGISTRATION_DEPTH_TO_COLOR; + } + + if (g_device.isImageRegistrationModeSupported(newMode)) + { + g_device.setImageRegistrationMode(newMode); + } + else + { + displayError("Couldn't change image registration to unsupported mode"); + } + +} + +openni::VideoStream* getSeekingStream(openni::VideoFrameRef*& pCurFrame) +{ + if (g_pPlaybackControl == NULL) + { + return NULL; + } + + if (g_bIsDepthOn) + { + pCurFrame = &g_depthFrame; + return &g_depthStream; + } + else if (g_bIsColorOn) + { + pCurFrame = &g_colorFrame; + return &g_colorStream; + } + else if (g_bIsIROn) + { + pCurFrame = &g_irFrame; + return &g_irStream; + } + else + { + return NULL; + } +} + +void seekStream(openni::VideoStream* pStream, openni::VideoFrameRef* pCurFrame, int frameId) +{ + int numberOfFrames = 0; + + // Get number of frames + numberOfFrames = g_pPlaybackControl->getNumberOfFrames(*pStream); + + // Seek + openni::Status rc = g_pPlaybackControl->seek(*pStream, frameId); + if (rc == openni::STATUS_OK) + { + // Read next frame from all streams. + if (g_bIsDepthOn) + { + g_depthStream.readFrame(&g_depthFrame); + } + if (g_bIsColorOn) + { + g_colorStream.readFrame(&g_colorFrame); + } + if (g_bIsIROn) + { + g_irStream.readFrame(&g_irFrame); + } + + // the new frameId might be different than expected (due to clipping to edges) + frameId = pCurFrame->getFrameIndex(); + + displayMessage("Current frame: %u/%u", frameId, numberOfFrames); + } + else if ((rc == openni::STATUS_NOT_IMPLEMENTED) || (rc == openni::STATUS_NOT_SUPPORTED) || (rc == openni::STATUS_BAD_PARAMETER) || (rc == openni::STATUS_NO_DEVICE)) + { + displayError("Seeking is not supported"); + } + else + { + displayError("Error seeking to frame:\n%s", openni::OpenNI::getExtendedError()); + } +} + +void seekFrame(int nDiff) +{ + // Make sure seek is required. + if (nDiff == 0) + { + return; + } + + openni::VideoStream* pStream = NULL; + openni::VideoFrameRef* pCurFrame = NULL; + + pStream = getSeekingStream(pCurFrame); + if (pStream == NULL) + return; + + int frameId = pCurFrame->getFrameIndex(); + // Calculate the new frame ID + frameId = (frameId + nDiff < 1) ? 1 : frameId + nDiff; + + seekStream(pStream, pCurFrame, frameId); +} + +void seekFrameAbs(int frameId) +{ + openni::VideoStream* pStream = NULL; + openni::VideoFrameRef* pCurFrame = NULL; + + pStream = getSeekingStream(pCurFrame); + if (pStream == NULL) + return; + + seekStream(pStream, pCurFrame, frameId); +} + +void toggleStreamState(openni::VideoStream& stream, openni::VideoFrameRef& frame, bool& isOn, openni::SensorType type, const char* name) +{ + openni::Status nRetVal = openni::STATUS_OK; + + if (!stream.isValid()) + { + nRetVal = stream.create(g_device, type); + if (nRetVal != openni::STATUS_OK) + { + displayError("Failed to create %s stream:\n%s", name, openni::OpenNI::getExtendedError()); + return; + } + } + + if (isOn) + { + stream.stop(); + frame.release(); + } + else + { + nRetVal = stream.start(); + if (nRetVal != openni::STATUS_OK) + { + displayError("Failed to start %s stream:\n%s", name, openni::OpenNI::getExtendedError()); + return; + } + } + + isOn = !isOn; +} + +void toggleDepthState(int) +{ + toggleStreamState(g_depthStream, g_depthFrame, g_bIsDepthOn, openni::SENSOR_DEPTH, "depth"); +} + +void toggleColorState(int) +{ + toggleStreamState(g_colorStream, g_colorFrame, g_bIsColorOn, openni::SENSOR_COLOR, "color"); +} + +void toggleIRState(int) +{ + toggleStreamState(g_irStream, g_irFrame, g_bIsIROn, openni::SENSOR_IR, "IR"); +} + +bool isDepthOn() +{ + return (g_bIsDepthOn); +} + +bool isColorOn() +{ + return (g_bIsColorOn); +} + +bool isIROn() +{ + return (g_bIsIROn); +} + +const openni::SensorInfo* getDepthSensorInfo() +{ + return g_depthSensorInfo; +} + +const openni::SensorInfo* getColorSensorInfo() +{ + return g_colorSensorInfo; +} + +const openni::SensorInfo* getIRSensorInfo() +{ + return g_irSensorInfo; +} + +void setDepthVideoMode(int mode) +{ + bool bIsStreamOn = g_bIsDepthOn; + if (bIsStreamOn) + { + g_bIsDepthOn = false; + g_depthStream.stop(); + } + + g_depthStream.setVideoMode(g_depthSensorInfo->getSupportedVideoModes()[mode]); + if (bIsStreamOn) + { + g_depthStream.start(); + g_bIsDepthOn = true; + } +} + +void setColorVideoMode(int mode) +{ + bool bIsStreamOn = g_bIsColorOn; + if (bIsStreamOn) + { + g_bIsColorOn = false; + g_colorStream.stop(); + } + + g_colorFrame.release(); + g_colorStream.setVideoMode(g_colorSensorInfo->getSupportedVideoModes()[mode]); + if (bIsStreamOn) + { + g_colorStream.start(); + g_bIsColorOn = true; + } +} + +void setIRVideoMode(int mode) +{ + bool bIsStreamOn = g_bIsIROn; + if (bIsStreamOn) + { + g_bIsIROn = false; + g_irStream.stop(); + } + + g_irFrame.release(); + g_irStream.setVideoMode(g_irSensorInfo->getSupportedVideoModes()[mode]); + if (bIsStreamOn) + { + g_irStream.start(); + g_bIsIROn = true; + } +} + +void toggleDepthMirror(int) +{ + g_depthStream.setMirroringEnabled(!g_depthStream.getMirroringEnabled()); +} + +void toggleColorMirror(int) +{ + g_colorStream.setMirroringEnabled(!g_colorStream.getMirroringEnabled()); +} + +void toggleIRMirror(int) +{ + g_irStream.setMirroringEnabled(!g_irStream.getMirroringEnabled()); +} + +void toggleImageAutoExposure(int) +{ + if (g_colorStream.getCameraSettings() == NULL) + { + displayError("Color stream doesn't support camera settings"); + return; + } + g_colorStream.getCameraSettings()->setAutoExposureEnabled(!g_colorStream.getCameraSettings()->getAutoExposureEnabled()); + displayMessage("Auto Exposure: %s", g_colorStream.getCameraSettings()->getAutoExposureEnabled() ? "ON" : "OFF"); +} + +void toggleImageAutoWhiteBalance(int) +{ + if (g_colorStream.getCameraSettings() == NULL) + { + displayError("Color stream doesn't support camera settings"); + return; + } + g_colorStream.getCameraSettings()->setAutoWhiteBalanceEnabled(!g_colorStream.getCameraSettings()->getAutoWhiteBalanceEnabled()); + displayMessage("Auto White balance: %s", g_colorStream.getCameraSettings()->getAutoWhiteBalanceEnabled() ? "ON" : "OFF"); +} + +void changeImageExposure(int delta) +{ + if (g_colorStream.getCameraSettings() == NULL) + { + displayError("Color stream doesn't support camera settings"); + return; + } + int exposure = g_colorStream.getCameraSettings()->getExposure(); + openni::Status rc = g_colorStream.getCameraSettings()->setExposure(exposure + delta); + if (rc != openni::STATUS_OK) + { + displayMessage("Can't change exposure"); + return; + } + displayMessage("Changed exposure to: %d", g_colorStream.getCameraSettings()->getExposure()); +} +void changeImageGain(int delta) +{ + if (g_colorStream.getCameraSettings() == NULL) + { + displayError("Color stream doesn't support camera settings"); + return; + } + int gain = g_colorStream.getCameraSettings()->getGain(); + openni::Status rc = g_colorStream.getCameraSettings()->setGain(gain + delta); + if (rc != openni::STATUS_OK) + { + displayMessage("Can't change gain"); + return; + } + displayMessage("Changed gain to: %d", g_colorStream.getCameraSettings()->getGain()); +} + +void setStreamCropping(openni::VideoStream& stream, int originX, int originY, int width, int height) +{ + if (!stream.isValid()) + { + displayMessage("Stream does not exist!"); + return; + } + + if (!stream.isCroppingSupported()) + { + displayMessage("Stream does not support cropping!"); + return; + } + + openni::Status nRetVal = stream.setCropping(originX, originY, width, height); + if (nRetVal != openni::STATUS_OK) + { + displayMessage("Failed to set cropping: %s", xnGetStatusString(nRetVal)); + return; + } +} + + +void resetStreamCropping(openni::VideoStream& stream) +{ + if (!stream.isValid()) + { + displayMessage("Stream does not exist!"); + return; + } + + if (!stream.isCroppingSupported()) + { + displayMessage("Stream does not support cropping!"); + return; + } + + openni::Status nRetVal = stream.resetCropping(); + if (nRetVal != openni::STATUS_OK) + { + displayMessage("Failed to reset cropping: %s", xnGetStatusString(nRetVal)); + return; + } +} + +void resetDepthCropping(int) +{ + getDepthStream().resetCropping(); +} + +void resetColorCropping(int) +{ + getColorStream().resetCropping(); +} + +void resetIRCropping(int) +{ + getIRStream().resetCropping(); +} + +void resetAllCropping(int) +{ + if (getDepthStream().isValid()) + resetDepthCropping(0); + + if (getColorStream().isValid()) + resetColorCropping(0); + + if (getIRStream().isValid()) + resetIRCropping(0); +} + +void togglePlaybackRepeat(int /*ignored*/) +{ + if (g_pPlaybackControl == NULL) + { + return; + } + + bool bLoop = g_pPlaybackControl->getRepeatEnabled(); + bLoop = !bLoop; + g_pPlaybackControl->setRepeatEnabled(bLoop); + char msg[100]; + sprintf(msg,"Repeat playback: %s", (bLoop ? "ON" : "OFF")); + displayMessage(msg); +} + +openni::Status setPlaybackSpeed(float speed) +{ + if (g_pPlaybackControl == NULL) + { + return openni::STATUS_NOT_SUPPORTED; + } + return g_pPlaybackControl->setSpeed(speed); +} + +float getPlaybackSpeed() +{ + if (g_pPlaybackControl == NULL) + { + return 0.0f; + } + return g_pPlaybackControl->getSpeed(); +} + +void changePlaybackSpeed(int ratioDiff) +{ + float ratio = (float)pow(2.0, ratioDiff); + float speed = getPlaybackSpeed() * ratio; + if (speed < 0) + { + speed = 0; + } + openni::Status rc = setPlaybackSpeed(speed); + if (rc == openni::STATUS_OK) + { + if (speed == 0) + { + displayMessage("Playback speed set to fastest"); + } + else + { + displayMessage("Playback speed set to x%.2f", speed); + } + } + else if ((rc == openni::STATUS_NOT_IMPLEMENTED) || (rc == openni::STATUS_NOT_SUPPORTED) || (rc == openni::STATUS_BAD_PARAMETER)) + { + displayError("Playback speed is not supported"); + } + else + { + displayError("Error setting playback speed:\n%s", openni::OpenNI::getExtendedError()); + } +} + +openni::Device& getDevice() +{ + return g_device; +} + +openni::VideoStream& getDepthStream() +{ + return g_depthStream; +} +openni::VideoStream& getColorStream() +{ + return g_colorStream; +} +openni::VideoStream& getIRStream() +{ + return g_irStream; +} + +openni::VideoFrameRef& getDepthFrame() +{ + return g_depthFrame; +} +openni::VideoFrameRef& getColorFrame() +{ + return g_colorFrame; +} +openni::VideoFrameRef& getIRFrame() +{ + return g_irFrame; +} + +bool g_bFrameSyncOn = false; +void toggleFrameSync(int) +{ + if (g_bFrameSyncOn) + { + g_device.setDepthColorSyncEnabled(false); + displayMessage("Frame sync off"); + } + else + { + openni::Status rc = g_device.setDepthColorSyncEnabled(true); + if (rc != openni::STATUS_OK) + { + displayMessage("Can't frame sync"); + return; + } + displayMessage("Frame sync on"); + } + g_bFrameSyncOn = !g_bFrameSyncOn; +} + +bool convertDepthPointToColor(int depthX, int depthY, openni::DepthPixel depthZ, int* pColorX, int* pColorY) +{ + if (!g_depthStream.isValid() || !g_colorStream.isValid()) + return false; + + return (openni::STATUS_OK == openni::CoordinateConverter::convertDepthToColor(g_depthStream, g_colorStream, depthX, depthY, depthZ, pColorX, pColorY)); +} diff --git a/Source/Tools/NiViewer/Device.h b/Source/Tools/NiViewer/Device.h new file mode 100644 index 0000000..a240b56 --- /dev/null +++ b/Source/Tools/NiViewer/Device.h @@ -0,0 +1,145 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __DEVICE_H__ +#define __DEVICE_H__ + +// -------------------------------- +// Includes +// -------------------------------- +#include + +// -------------------------------- +// Defines +// -------------------------------- +#define MAX_STRINGS 20 + +// -------------------------------- +// Types +// -------------------------------- +typedef struct +{ + int nValuesCount; + unsigned int pValues[MAX_STRINGS]; + const char* pValueToName[MAX_STRINGS]; +} DeviceParameter; + +typedef struct +{ + int nValuesCount; + const char* pValues[MAX_STRINGS]; +} DeviceStringProperty; + +typedef enum SensorOpenType +{ + SENSOR_OFF, + SENSOR_ON, + SENSOR_TRY +} SensorOpenType; + +typedef struct +{ + SensorOpenType openDepth; + SensorOpenType openColor; + SensorOpenType openIR; +} DeviceConfig; + +// -------------------------------- +// Global Variables +// -------------------------------- +extern DeviceStringProperty g_PrimaryStream; +extern DeviceParameter g_Registration; + +// -------------------------------- +// Function Declarations +// -------------------------------- +openni::Status openDevice(const char* uri, DeviceConfig config); +openni::Status openDeviceFromList(DeviceConfig config); +void closeDevice(); +void readFrame(); +void changeRegistration(int nValue); +void changePrimaryStream(int nValue); +void toggleMirror(int); +void seekFrame(int nDiff); +void seekFrameAbs(int frameId); +void toggleDepthState(int nDummy); +void toggleColorState(int nDummy); +void toggleIRState(int nDummy); +void toggleAudioState(int nDummy); +void getDepthFormats(const char** pNames, unsigned int* pValues, int* pCount); +void getImageFormats(const char** pNames, unsigned int* pValues, int* pCount); +void getAudioFormats(const char** pNames, unsigned int* pValues, int* pCount); +void getPrimaryStreams(const char** pNames, unsigned int* pValues, int* pCount); +bool isDepthOn(); +bool isColorOn(); +bool isIROn(); +bool isAudioOn(); +bool isPlayerOn(); +const char* getFormatName(openni::PixelFormat format); + +const openni::SensorInfo* getDepthSensorInfo(); +const openni::SensorInfo* getColorSensorInfo(); +const openni::SensorInfo* getIRSensorInfo(); + +void setDepthVideoMode(int mode); +void setColorVideoMode(int mode); +void setIRVideoMode(int mode); + +void toggleDepthMirror(int); +void toggleColorMirror(int); +void toggleIRMirror(int); + +void toggleImageAutoExposure(int); +void toggleImageAutoWhiteBalance(int); +void changeImageExposure(int); +void changeImageGain(int); + +void toggleCloseRange(int); +void toggleImageRegistration(int); + +void setStreamCropping(openni::VideoStream& stream, int originX, int originY, int width, int height); +void resetDepthCropping(int); +void resetColorCropping(int); +void resetIRCropping(int); +void resetAllCropping(int); + +openni::Device& getDevice(); +openni::VideoStream& getDepthStream(); +openni::VideoStream& getColorStream(); +openni::VideoStream& getIRStream(); + +openni::VideoFrameRef& getDepthFrame(); +openni::VideoFrameRef& getColorFrame(); +openni::VideoFrameRef& getIRFrame(); +// const DepthMetaData* getDepthMetaData(); +// const ImageMetaData* getImageMetaData(); +// const IRMetaData* getIRMetaData(); +// const AudioMetaData* getAudioMetaData(); + +void toggleFrameSync(int); + +void togglePlaybackRepeat(int /*ignored*/); +openni::Status setPlaybackSpeed(float speed); +float getPlaybackSpeed(); +void changePlaybackSpeed(int ratioDiff); + +bool convertDepthPointToColor(int depthX, int depthY, openni::DepthPixel DepthZ, int* pColorX, int* pColorY); + +#endif //__DEVICE_H__ diff --git a/Source/Tools/NiViewer/Draw.cpp b/Source/Tools/NiViewer/Draw.cpp new file mode 100644 index 0000000..9bc75b1 --- /dev/null +++ b/Source/Tools/NiViewer/Draw.cpp @@ -0,0 +1,1835 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Includes +// -------------------------------- +#include "OpenNI.h" +#include "XnLib.h" +#include "Draw.h" +#include "Device.h" +#include "Keyboard.h" +#include "XnMath.h" +#include "Capture.h" +#if (XN_PLATFORM == XN_PLATFORM_MACOSX) + #include + #include +#else + #include + #include +#endif +#include "MouseInput.h" +#include + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + #ifdef __INTEL_COMPILER + #include + #else + #include + #endif +#endif + +// -------------------------------- +// Defines +// -------------------------------- +#define MAX_DEPTH XN_MAX_UINT16 + +#define YUV422_U 0 +#define YUV422_Y1 1 +#define YUV422_V 2 +#define YUV422_Y2 3 +#define YUV422_BPP 4 +#define YUV_RED 0 +#define YUV_GREEN 1 +#define YUV_BLUE 2 +#define YUV_ALPHA 3 +#define YUV_RGBA_BPP 4 +#define YUYV_Y1 0 +#define YUYV_U 1 +#define YUYV_Y2 2 +#define YUYV_V 3 +#define YUYV_BPP 4 +#define YUV_RED 0 +#define YUV_GREEN 1 +#define YUV_BLUE 2 +#define YUV_RGB_BPP 3 + +// -------------------------------- +// Types +// -------------------------------- +typedef enum +{ + NOTIFICATION_MESSAGE, + WARNING_MESSAGE, + ERROR_MESSAGE, + FATAL_MESSAGE, + NUM_DRAW_MESSAGE_TYPES +} DrawMessageType; + +typedef struct +{ + StreamsDrawConfig Streams; + bool bShowPointer; + bool bShowMessage; + DrawMessageType messageType; + bool bHelp; + XnChar strErrorState[256]; + IntRect DepthLocation; + IntRect ColorLocation; +} DrawConfig; + +typedef struct +{ + const char* csName; + StreamsDrawConfig Config; +} DrawConfigPreset; + +typedef struct XnTextureMap +{ + IntPair Size; + IntPair OrigSize; + unsigned char* pMap; + unsigned int nBytesPerPixel; + GLuint nID; + GLenum nFormat; + bool bInitialized; + IntPair CurSize; +} XnTextureMap; + +// -------------------------------- +// Global Variables +// -------------------------------- +DrawConfig g_DrawConfig; + +XnUInt8 PalletIntsR [256] = {0}; +XnUInt8 PalletIntsG [256] = {0}; +XnUInt8 PalletIntsB [256] = {0}; + +/* Linear Depth Histogram */ +float g_pDepthHist[MAX_DEPTH]; + +const char* g_DepthDrawColoring[NUM_OF_DEPTH_DRAW_TYPES]; +const char* g_ColorDrawColoring[NUM_OF_COLOR_DRAW_TYPES]; + +typedef struct DrawUserInput +{ + SelectionState State; + IntRect Rect; + IntPair Cursor; +} DrawUserInput; + +DrawUserInput g_DrawUserInput; + +float g_fMaxDepth = 0; + +DrawConfigPreset g_Presets[PRESET_COUNT] = +{ + // NAME, Depth_Type, Transparency Image_Type Arrangement + { "Standard Deviation", { { STANDARD_DEVIATION, 1 }, { COLOR_OFF }, OVERLAY } }, // Obsolete + { "Depth Histogram", { { LINEAR_HISTOGRAM, 1 }, { COLOR_OFF }, OVERLAY } }, + { "Psychedelic Depth [Centimeters]", { { PSYCHEDELIC, 1 }, { COLOR_OFF }, OVERLAY } }, + { "Psychedelic Depth [Millimeters]", { { PSYCHEDELIC_SHADES, 1 }, { COLOR_OFF }, OVERLAY } }, + { "Rainbow Depth", { { CYCLIC_RAINBOW_HISTOGRAM, 1 }, { COLOR_OFF }, OVERLAY } }, + { "Depth masked Color", { { DEPTH_OFF, 1 }, { DEPTH_MASKED_COLOR }, OVERLAY } }, + { "Background Removal", { { DEPTH_OFF, 1 }, { DEPTH_MASKED_COLOR }, OVERLAY } }, // Obsolete + { "Side by Side", { { LINEAR_HISTOGRAM, 1 }, { COLOR_NORMAL }, SIDE_BY_SIDE } }, + { "Depth on Color", { { LINEAR_HISTOGRAM, 1 }, { COLOR_NORMAL }, OVERLAY } }, + { "Transparent Depth on Color", { { LINEAR_HISTOGRAM, 0.6 }, { COLOR_NORMAL }, OVERLAY } }, + { "Rainbow Depth on Color", { { RAINBOW, 0.6 }, { COLOR_NORMAL }, OVERLAY } }, + { "Cyclic Rainbow Depth on Color", { { CYCLIC_RAINBOW, 0.6 }, { COLOR_NORMAL }, OVERLAY } }, + { "Color Only", { { DEPTH_OFF, 1 }, { COLOR_NORMAL }, OVERLAY } }, +}; + +/* Texture maps for depth and color */ +XnTextureMap g_texDepth = {{0}}; +XnTextureMap g_texColor = {{0}}; + +/* A user message to be displayed. */ +char g_csUserMessage[256]; + +bool g_bFullScreen = true; +bool g_bFirstTimeNonFull = true; +IntPair g_NonFullWinSize = { WIN_SIZE_X, WIN_SIZE_Y }; + +// -------------------------------- +// Textures +// -------------------------------- +int GetPowerOfTwo(int num) +{ + int result = 1; + + while (result < num) + result <<= 1; + + return result; +} + +void TextureMapInit(XnTextureMap* pTex, int nSizeX, int nSizeY, unsigned int nBytesPerPixel, int nCurX, int nCurY) +{ + // check if something changed + if (pTex->bInitialized && pTex->OrigSize.X == nSizeX && pTex->OrigSize.Y == nSizeY) + { + if (pTex->CurSize.X != nCurX || pTex->CurSize.Y != nCurY) + { + // clear map + xnOSMemSet(pTex->pMap, 0, pTex->Size.X * pTex->Size.Y * pTex->nBytesPerPixel); + + // update + pTex->CurSize.X = nCurX; + pTex->CurSize.Y = nCurY; + return; + } + } + + // free memory if it was allocated + if (pTex->pMap != NULL) + { + delete[] pTex->pMap; + pTex->pMap = NULL; + } + + // update it all + pTex->OrigSize.X = nSizeX; + pTex->OrigSize.Y = nSizeY; + pTex->Size.X = GetPowerOfTwo(nSizeX); + pTex->Size.Y = GetPowerOfTwo(nSizeY); + pTex->nBytesPerPixel = nBytesPerPixel; + pTex->CurSize.X = nCurX; + pTex->CurSize.Y = nCurY; + pTex->pMap = new unsigned char[pTex->Size.X * pTex->Size.Y * nBytesPerPixel]; + xnOSMemSet(pTex->pMap, 0, pTex->Size.X * pTex->Size.Y * nBytesPerPixel); + + if (!pTex->bInitialized) + { + glGenTextures(1, &pTex->nID); + glBindTexture(GL_TEXTURE_2D, pTex->nID); + + switch (pTex->nBytesPerPixel) + { + case 3: + pTex->nFormat = GL_RGB; + break; + case 4: + pTex->nFormat = GL_RGBA; + break; + } + + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + pTex->bInitialized = TRUE; + } +} + +inline unsigned char* TextureMapGetLine(XnTextureMap* pTex, unsigned int nLine) +{ + return &pTex->pMap[nLine * pTex->Size.X * pTex->nBytesPerPixel]; +} + +void TextureMapSetPixel(XnTextureMap* pTex, int x, int y, int red, int green, int blue) +{ + if (x < 0 || y < 0 || x >= (int)pTex->OrigSize.X || y >= (int)pTex->OrigSize.Y) + return; + + unsigned char* pPixel = TextureMapGetLine(pTex, y) + x * pTex->nBytesPerPixel; + pPixel[0] = red; + pPixel[1] = green; + pPixel[2] = blue; + + if (pTex->nBytesPerPixel > 3) + pPixel[3] = 255; +} + +void TextureMapDrawCursor(XnTextureMap* pTex, IntPair cursor, int red = 255, int green = 0, int blue = 0) +{ + // marked pixel + TextureMapSetPixel(pTex, cursor.X, cursor.Y, red, green, 0); + + // top left marker + TextureMapSetPixel(pTex, cursor.X-2, cursor.Y-2, red, green, blue); + TextureMapSetPixel(pTex, cursor.X-2, cursor.Y-1, red, green, blue); + TextureMapSetPixel(pTex, cursor.X-1, cursor.Y-2, red, green, blue); + + // top right marker + TextureMapSetPixel(pTex, cursor.X+2, cursor.Y-2, red, green, blue); + TextureMapSetPixel(pTex, cursor.X+2, cursor.Y-1, red, green, blue); + TextureMapSetPixel(pTex, cursor.X+1, cursor.Y-2, red, green, blue); + + // bottom left marker + TextureMapSetPixel(pTex, cursor.X-2, cursor.Y+2, red, green, blue); + TextureMapSetPixel(pTex, cursor.X-2, cursor.Y+1, red, green, blue); + TextureMapSetPixel(pTex, cursor.X-1, cursor.Y+2, red, green, blue); + + // bottom right marker + TextureMapSetPixel(pTex, cursor.X+2, cursor.Y+2, red, green, blue); + TextureMapSetPixel(pTex, cursor.X+2, cursor.Y+1, red, green, blue); + TextureMapSetPixel(pTex, cursor.X+1, cursor.Y+2, red, green, blue); +} + +void TextureMapUpdate(XnTextureMap* pTex) +{ + // set current texture object + glBindTexture(GL_TEXTURE_2D, pTex->nID); + + // set the current image to the texture + glTexImage2D(GL_TEXTURE_2D, 0, pTex->nFormat, pTex->Size.X, pTex->Size.Y, 0, pTex->nFormat, GL_UNSIGNED_BYTE, pTex->pMap); +} + +void TextureMapDraw(XnTextureMap* pTex, IntRect* pLocation) +{ + // set current texture object + glBindTexture(GL_TEXTURE_2D, pTex->nID); + + // turn on texture mapping + glEnable(GL_TEXTURE_2D); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // set drawing mode to rectangles + glBegin(GL_QUADS); + + // set the color of the polygon + glColor4f(1, 1, 1, 1); + + // upper left + glTexCoord2f(0, 0); + glVertex2f(pLocation->uLeft, pLocation->uBottom); + // upper right + glTexCoord2f((float)pTex->OrigSize.X/(float)pTex->Size.X, 0); + glVertex2f(pLocation->uRight, pLocation->uBottom); + // bottom right + glTexCoord2f((float)pTex->OrigSize.X/(float)pTex->Size.X, (float)pTex->OrigSize.Y/(float)pTex->Size.Y); + glVertex2f(pLocation->uRight, pLocation->uTop); + // bottom left + glTexCoord2f(0, (float)pTex->OrigSize.Y/(float)pTex->Size.Y); + glVertex2f(pLocation->uLeft, pLocation->uTop); + + glEnd(); + + // turn off texture mapping + glDisable(GL_TEXTURE_2D); + + glDisable(GL_BLEND); +} + +// -------------------------------- +// Code +// -------------------------------- +void CreateRainbowPallet() +{ + unsigned char r, g, b; + for (int i=0; i<256; i++) + { + if (i<=29) + { + r = (unsigned char)(129.36-i*4.36); + g = 0; + b = (unsigned char)255; + } + else if (i<=86) + { + r = 0; + g = (unsigned char)(-133.54+i*4.52); + b = (unsigned char)255; + } + else if (i<=141) + { + r = 0; + g = (unsigned char)255; + b = (unsigned char)(665.83-i*4.72); + } + else if (i<=199) + { + r = (unsigned char)(-635.26+i*4.47); + g = (unsigned char)255; + b = 0; + } + else + { + r = (unsigned char)255; + g = (unsigned char)(1166.81-i*4.57); + b = 0; + } + + PalletIntsR[i] = r; + PalletIntsG[i] = g; + PalletIntsB[i] = b; + } +} + +void glPrintString(void *font, const char *str) +{ + int i,l = (int)strlen(str); + + for(i=0; i= location.uLeft && + selection.uRight <= location.uRight && + selection.uBottom >= location.uBottom && + selection.uTop <= location.uTop) + { + IntRect cropRect; + cropRect.uBottom = Mode.getResolutionY() * (selection.uBottom - location.uBottom) / (location.uTop - location.uBottom); + cropRect.uTop = Mode.getResolutionY() * (selection.uTop - location.uBottom) / (location.uTop - location.uBottom); + cropRect.uLeft = Mode.getResolutionX() * (selection.uLeft - location.uLeft) / (location.uRight - location.uLeft); + cropRect.uRight = Mode.getResolutionX() * (selection.uRight - location.uLeft) / (location.uRight - location.uLeft); + + int originX, originY, width, height; + + originX = cropRect.uLeft; + originY = cropRect.uBottom; + width = cropRect.uRight - cropRect.uLeft; + height = cropRect.uTop - cropRect.uBottom; + + if ((originX % dividedBy) != 0) + originX -= (originX % dividedBy); + if ((width % dividedBy) != 0) + width += dividedBy - (width % dividedBy); + + setStreamCropping(stream, originX, originY, width, height); + } +} + +void drawSelectionChanged(SelectionState state, IntRect selection) +{ + g_DrawUserInput.State = state; + g_DrawUserInput.Rect = selection; + + if (state == SELECTION_DONE) + { + // Crop depth + if (getDepthStream().isValid() && isDepthOn() && g_DrawConfig.Streams.Depth.Coloring != DEPTH_OFF) + { + drawCropStream(getDepthStream(), g_DrawConfig.DepthLocation, selection, 2); + } + + // Crop image + if (getColorStream().isValid() && isColorOn() && g_DrawConfig.Streams.Color.Coloring != COLOR_OFF) + { + drawCropStream(getColorStream(), g_DrawConfig.ColorLocation, selection, 4); + } + + // Crop IR + if (getIRStream().isValid() && isIROn() && g_DrawConfig.Streams.Color.Coloring != COLOR_OFF) + { + drawCropStream(getIRStream(), g_DrawConfig.ColorLocation, selection, 4); + } + } +} + +void drawCursorMoved(IntPair location) +{ + g_DrawUserInput.Cursor = location; +} + +void drawInit() +{ + g_DepthDrawColoring[DEPTH_OFF] = "Off"; + g_DepthDrawColoring[LINEAR_HISTOGRAM] = "Linear Histogram"; + g_DepthDrawColoring[PSYCHEDELIC] = "Psychedelic"; + g_DepthDrawColoring[PSYCHEDELIC_SHADES] = "Psychedelic (Millimeters)"; + g_DepthDrawColoring[RAINBOW] = "Rainbow"; + g_DepthDrawColoring[CYCLIC_RAINBOW] = "Cyclic Rainbow"; + g_DepthDrawColoring[CYCLIC_RAINBOW_HISTOGRAM] = "Cyclic Rainbow Histogram"; + g_DepthDrawColoring[STANDARD_DEVIATION] = "Standard Deviation"; + + g_ColorDrawColoring[COLOR_OFF] = "Off"; + g_ColorDrawColoring[COLOR_NORMAL] = "Normal"; + g_ColorDrawColoring[DEPTH_MASKED_COLOR] = "Depth Masked Color"; + + CreateRainbowPallet(); + + setPreset(7); + + mouseInputRegisterForSelectionRectangle(drawSelectionChanged); + mouseInputRegisterForCursorMovement(drawCursorMoved); +} + +void togglePointerMode(int) +{ + g_DrawConfig.bShowPointer = !g_DrawConfig.bShowPointer; +} + +void toggleHelpScreen(int) +{ + g_DrawConfig.bHelp = !g_DrawConfig.bHelp; +} + +void calculateHistogram() +{ + xnOSMemSet(g_pDepthHist, 0, MAX_DEPTH*sizeof(float)); + int nNumberOfPoints = 0; + + openni::DepthPixel nValue; + + openni::VideoStream& depthGen = getDepthStream(); + + if (!depthGen.isValid() || !getDepthFrame().isValid()) + return; + + const openni::DepthPixel* pDepth = (const openni::DepthPixel*)getDepthFrame().getData(); + const openni::DepthPixel* pDepthEnd = pDepth + (getDepthFrame().getDataSize() / sizeof(openni::DepthPixel)); + + while (pDepth != pDepthEnd) + { + nValue = *pDepth; + + XN_ASSERT(nValue <= MAX_DEPTH); + + if (nValue != 0) + { + g_pDepthHist[nValue]++; + nNumberOfPoints++; + } + + pDepth++; + } + + XnUInt32 nIndex; + for (nIndex=1; nIndex> 8, 0), 255); + cG = XN_MIN(XN_MAX((nC - 100 * nD - 208 * nE) >> 8, 0), 255); + cB = XN_MIN(XN_MAX((nC + 516 * nD ) >> 8, 0), 255); + cA = 255; +} + +void YUV422ToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32 nRGBSize) +{ + const XnUInt8* pCurrYUV = pYUVImage; + XnUInt8* pCurrRGB = pRGBImage; + const XnUInt8* pLastYUV = pYUVImage + nYUVSize - YUV422_BPP; + XnUInt8* pLastRGB = pRGBImage + nRGBSize - YUV_RGBA_BPP; + + while (pCurrYUV <= pLastYUV && pCurrRGB <= pLastRGB) + { + YUV444ToRGBA(pCurrYUV[YUV422_Y1], pCurrYUV[YUV422_U], pCurrYUV[YUV422_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE], pCurrRGB[YUV_ALPHA]); + pCurrRGB += YUV_RGBA_BPP; + YUV444ToRGBA(pCurrYUV[YUV422_Y2], pCurrYUV[YUV422_U], pCurrYUV[YUV422_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE], pCurrRGB[YUV_ALPHA]); + pCurrRGB += YUV_RGBA_BPP; + pCurrYUV += YUV422_BPP; + } +} + +#endif + +#if (XN_PLATFORM == XN_PLATFORM_WIN32) + +void YUYVToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBAImage, XnUInt32 nYUVSize, XnUInt32 nRGBSize) +{ + const XnUInt8* pYUVLast = pYUVImage + nYUVSize - 8; + XnUInt8* pRGBLast = pRGBAImage + nRGBSize - 16; + + const __m128 minus128 = _mm_set_ps1(-128); + const __m128 plus113983 = _mm_set_ps1(1.13983F); + const __m128 minus039466 = _mm_set_ps1(-0.39466F); + const __m128 minus058060 = _mm_set_ps1(-0.58060F); + const __m128 plus203211 = _mm_set_ps1(2.03211F); + const __m128 zero = _mm_set_ps1(0); + const __m128 plus255 = _mm_set_ps1(255); + + // define YUV floats + __m128 y; + __m128 u; + __m128 v; + + __m128 temp; + + // define RGB floats + __m128 r; + __m128 g; + __m128 b; + + // define RGB integers + __m128i iR; + __m128i iG; + __m128i iB; + + XnUInt32* piR = (XnUInt32*)&iR; + XnUInt32* piG = (XnUInt32*)&iG; + XnUInt32* piB = (XnUInt32*)&iB; + + while (pYUVImage <= pYUVLast && pRGBAImage <= pRGBLast) + { + // process 4 pixels at once (values should be ordered backwards) + y = _mm_set_ps(pYUVImage[YUYV_Y2 + YUYV_BPP], pYUVImage[YUYV_Y1 + YUYV_BPP], pYUVImage[YUYV_Y2], pYUVImage[YUYV_Y1]); + u = _mm_set_ps(pYUVImage[YUYV_U + YUYV_BPP], pYUVImage[YUYV_U + YUYV_BPP], pYUVImage[YUYV_U], pYUVImage[YUYV_U]); + v = _mm_set_ps(pYUVImage[YUYV_V + YUYV_BPP], pYUVImage[YUYV_V + YUYV_BPP], pYUVImage[YUYV_V], pYUVImage[YUYV_V]); + + u = _mm_add_ps(u, minus128); // u -= 128 + v = _mm_add_ps(v, minus128); // v -= 128 + + /* + + http://en.wikipedia.org/wiki/YUV + + From YUV to RGB: + R = Y + 1.13983 V + G = Y - 0.39466 U - 0.58060 V + B = Y + 2.03211 U + + */ + + temp = _mm_mul_ps(plus113983, v); + r = _mm_add_ps(y, temp); + + temp = _mm_mul_ps(minus039466, u); + g = _mm_add_ps(y, temp); + temp = _mm_mul_ps(minus058060, v); + g = _mm_add_ps(g, temp); + + temp = _mm_mul_ps(plus203211, u); + b = _mm_add_ps(y, temp); + + // make sure no value is smaller than 0 + r = _mm_max_ps(r, zero); + g = _mm_max_ps(g, zero); + b = _mm_max_ps(b, zero); + + // make sure no value is bigger than 255 + r = _mm_min_ps(r, plus255); + g = _mm_min_ps(g, plus255); + b = _mm_min_ps(b, plus255); + + // convert floats to int16 (there is no conversion to uint8, just to int8). + iR = _mm_cvtps_epi32(r); + iG = _mm_cvtps_epi32(g); + iB = _mm_cvtps_epi32(b); + + // extract the 4 pixels RGB values. + // because we made sure values are between 0 and 255, we can just take the lower byte + // of each INT16 + pRGBAImage[0] = piR[0]; + pRGBAImage[1] = piG[0]; + pRGBAImage[2] = piB[0]; + pRGBAImage[3] = 255; + + pRGBAImage[4] = piR[1]; + pRGBAImage[5] = piG[1]; + pRGBAImage[6] = piB[1]; + pRGBAImage[7] = 255; + + pRGBAImage[8] = piR[2]; + pRGBAImage[9] = piG[2]; + pRGBAImage[10] = piB[2]; + pRGBAImage[11] = 255; + + pRGBAImage[12] = piR[3]; + pRGBAImage[13] = piG[3]; + pRGBAImage[14] = piB[3]; + pRGBAImage[15] = 255; + + // advance the streams + pYUVImage += 8; + pRGBAImage += 16; + } +} + +#else // not Win32 + +void YUYVToRGB888(const XnUInt8* pYUVImage, XnUInt8* pRGBImage, XnUInt32 nYUVSize, XnUInt32 nRGBSize) +{ + const XnUInt8* pCurrYUV = pYUVImage; + XnUInt8* pCurrRGB = pRGBImage; + const XnUInt8* pLastYUV = pYUVImage + nYUVSize - YUYV_BPP; + XnUInt8* pLastRGB = pRGBImage + nRGBSize - YUV_RGBA_BPP; + + while (pCurrYUV <= pLastYUV && pCurrRGB <= pLastRGB) + { + YUV444ToRGBA(pCurrYUV[YUYV_Y1], pCurrYUV[YUYV_U], pCurrYUV[YUYV_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE], pCurrRGB[YUV_ALPHA]); + pCurrRGB += YUV_RGBA_BPP; + YUV444ToRGBA(pCurrYUV[YUYV_Y2], pCurrYUV[YUYV_U], pCurrYUV[YUYV_V], + pCurrRGB[YUV_RED], pCurrRGB[YUV_GREEN], pCurrRGB[YUV_BLUE], pCurrRGB[YUV_ALPHA]); + pCurrRGB += YUV_RGBA_BPP; + pCurrYUV += YUYV_BPP; + } +} + +#endif + +void drawClosedStream(IntRect* pLocation, const char* csStreamName) +{ + char csMessage[512]; + XnUInt32 nWritten; + xnOSStrFormat(csMessage, sizeof(csMessage), &nWritten, "%s stream is OFF", csStreamName); + void* pFont = GLUT_BITMAP_TIMES_ROMAN_24; + + int nWidth = glutBitmapLength(pFont, (const unsigned char*)csMessage); + int nXLocation = (pLocation->uRight + pLocation->uLeft - nWidth) / 2; + int nYLocation = (pLocation->uTop + pLocation->uBottom) / 2; + + glColor3f(1.0, 0, 0); + glRasterPos2i(nXLocation, nYLocation); + glPrintString(pFont, csMessage); +} + +void drawColor(IntRect* pLocation, IntPair* pPointer, int pointerRed, int pointerGreen, int pointerBlue) +{ + if (g_DrawConfig.Streams.Color.Coloring == COLOR_OFF) + return; + + if (!isColorOn() && !isIROn()) + { + drawClosedStream(pLocation, "Color"); + return; + } + + openni::VideoFrameRef colorMD; + int depthWidth = 0, depthHeight = 0, depthFullWidth = 0, depthFullHeight = 0; + int depthOriginX = 0, depthOriginY = 0; + + if (isColorOn()) + { + colorMD = getColorFrame(); + if (!colorMD.isValid()) + return; + } + else if (isIROn()) + { + colorMD = getIRFrame(); + } + else + return; + + if (!colorMD.isValid()) + return; + + if (colorMD.getFrameIndex() == 0) + { + return; + } + + openni::VideoFrameRef depthMetaData = getDepthFrame(); + + int width = colorMD.getWidth(); + int height = colorMD.getHeight(); + int fullWidth = colorMD.getVideoMode().getResolutionX(); + int fullHeight = colorMD.getVideoMode().getResolutionY(); + int originX = colorMD.getCropOriginX(); + int originY = colorMD.getCropOriginY(); + + XnUInt8* pColor = (XnUInt8*)colorMD.getData(); + bool useDepth = false; + openni::PixelFormat format = colorMD.getVideoMode().getPixelFormat(); + + openni::DepthPixel* pDepth = NULL; + + if (depthMetaData.isValid()) + { + useDepth = true; + depthWidth = depthMetaData.getWidth(); + depthHeight = depthMetaData.getHeight(); + depthFullWidth = depthMetaData.getVideoMode().getResolutionX(); + depthFullHeight = depthMetaData.getVideoMode().getResolutionY(); + depthOriginX = depthMetaData.getCropOriginX(); + depthOriginY = depthMetaData.getCropOriginY(); + + pDepth = (openni::DepthPixel*)depthMetaData.getData(); + } + + for (XnUInt16 nY = 0; nY < height; nY++) + { + XnUInt8* pTexture = TextureMapGetLine(&g_texColor, nY + originY) + originX*4; + + if (format == openni::PIXEL_FORMAT_YUV422) + { + YUV422ToRGB888(pColor, pTexture, width*2, g_texColor.Size.X*g_texColor.nBytesPerPixel); + pColor += width*2; + } + else if (format == openni::PIXEL_FORMAT_YUYV) + { + YUYVToRGB888(pColor, pTexture, width*2, g_texColor.Size.X*g_texColor.nBytesPerPixel); + pColor += width*2; + } + else + { + XnDouble dRealY = (nY + originY) / (XnDouble)fullHeight; + XnInt32 nDepthY = dRealY * depthFullHeight - depthOriginY; + + for (XnUInt16 nX = 0; nX < width; nX++, pTexture+=4) + { + XnInt32 nDepthIndex = 0; + + if (useDepth) + { + XnDouble dRealX = (nX + originX) / (XnDouble)fullWidth; + + XnInt32 nDepthX = dRealX * depthFullWidth - depthOriginX; + + if (nDepthX >= depthWidth || nDepthY >= depthHeight || nDepthX < 0 || nDepthY < 0) + { + nDepthIndex = -1; + } + else + { + nDepthIndex = nDepthY*depthWidth + nDepthX; + } + } + + switch (format) + { + case openni::PIXEL_FORMAT_RGB888: + pTexture[0] = pColor[0]; + pTexture[1] = pColor[1]; + pTexture[2] = pColor[2]; + pColor+=3; + break; + case openni::PIXEL_FORMAT_GRAY8: + pTexture[0] = pTexture[1] = pTexture[2] = *pColor; + pColor+=1; + break; + case openni::PIXEL_FORMAT_GRAY16: + pTexture[0] = pTexture[1] = pTexture[2] = *((XnUInt16*)pColor) >> 2; + pColor+=2; + break; + default: + assert(0); + return; + } + + // decide if pixel should be lit or not + if (g_DrawConfig.Streams.Color.Coloring == DEPTH_MASKED_COLOR && + (!depthMetaData.isValid() || nDepthIndex == -1 || pDepth[nDepthIndex] == 0)) + { + pTexture[3] = 0; + } + else + { + pTexture[3] = 255; + } + } + } + } + + if (pPointer != NULL) + { + TextureMapDrawCursor(&g_texColor, *pPointer, pointerRed, pointerGreen, pointerBlue); + } + + TextureMapUpdate(&g_texColor); + TextureMapDraw(&g_texColor, pLocation); +} + +void drawDepth(IntRect* pLocation, IntPair* pPointer) +{ + if (g_DrawConfig.Streams.Depth.Coloring != DEPTH_OFF) + { + if (!isDepthOn()) + { + drawClosedStream(pLocation, "Depth"); + return; + } + + openni::VideoFrameRef* pDepthMD = &getDepthFrame(); + + if (!pDepthMD->isValid()) + return; + + const openni::DepthPixel* pDepth = (openni::DepthPixel*)pDepthMD->getData(); + XN_ASSERT(pDepth); + + int width = pDepthMD->getWidth(); + int height = pDepthMD->getHeight(); + int originX = pDepthMD->getCropOriginX(); + int originY = pDepthMD->getCropOriginY(); + + if (pDepthMD->getFrameIndex() == 0) + { + return; + } + + // copy depth into texture-map + for (XnUInt16 nY = originY; nY < height + originY; nY++) + { + XnUInt8* pTexture = TextureMapGetLine(&g_texDepth, nY) + originX*4; + for (XnUInt16 nX = 0; nX < width; nX++, pDepth++, pTexture+=4) + { + XnUInt8 nRed = 0; + XnUInt8 nGreen = 0; + XnUInt8 nBlue = 0; + XnUInt8 nAlpha = g_DrawConfig.Streams.Depth.fTransparency*255; + + XnUInt16 nColIndex; + + switch (g_DrawConfig.Streams.Depth.Coloring) + { + case LINEAR_HISTOGRAM: + nRed = nGreen = g_pDepthHist[*pDepth]*255; + break; + case PSYCHEDELIC_SHADES: + nAlpha *= (((XnFloat)(*pDepth % 10) / 20) + 0.5); + case PSYCHEDELIC: + + switch ((*pDepth/10) % 10) + { + case 0: + nRed = 255; + break; + case 1: + nGreen = 255; + break; + case 2: + nBlue = 255; + break; + case 3: + nRed = 255; + nGreen = 255; + break; + case 4: + nGreen = 255; + nBlue = 255; + break; + case 5: + nRed = 255; + nBlue = 255; + break; + case 6: + nRed = 255; + nGreen = 255; + nBlue = 255; + break; + case 7: + nRed = 127; + nBlue = 255; + break; + case 8: + nRed = 255; + nBlue = 127; + break; + case 9: + nRed = 127; + nGreen = 255; + break; + } + break; + case RAINBOW: + nColIndex = (XnUInt16)((*pDepth / (g_fMaxDepth / 256))); + nRed = PalletIntsR[nColIndex]; + nGreen = PalletIntsG[nColIndex]; + nBlue = PalletIntsB[nColIndex]; + break; + case CYCLIC_RAINBOW: + nColIndex = (*pDepth % 256); + nRed = PalletIntsR[nColIndex]; + nGreen = PalletIntsG[nColIndex]; + nBlue = PalletIntsB[nColIndex]; + break; + case CYCLIC_RAINBOW_HISTOGRAM: + { + float fHist = g_pDepthHist[*pDepth]; + nColIndex = (*pDepth % 256); + nRed = PalletIntsR[nColIndex] * fHist; + nGreen = PalletIntsG[nColIndex] * fHist; + nBlue = PalletIntsB[nColIndex] * fHist; + break; + } + default: + assert(0); + return; + } + + pTexture[0] = nRed; + pTexture[1] = nGreen; + pTexture[2] = nBlue; + + if (*pDepth == 0) + pTexture[3] = 0; + else + pTexture[3] = nAlpha; + } + } + + if (pPointer != NULL) + { + TextureMapDrawCursor(&g_texDepth, *pPointer); + } + + TextureMapUpdate(&g_texDepth); + TextureMapDraw(&g_texDepth, pLocation); + } +} + +void drawPointerMode(IntPair* pPointer) +{ + char buf[512] = ""; + XnUInt32 chars; + int nCharWidth = glutBitmapWidth(GLUT_BITMAP_HELVETICA_18, '0'); + int nPointerValue = 0; + + XnDouble dTimestampDivider = 1E6; + + openni::VideoFrameRef* pDepthMD = &getDepthFrame(); + + if (pDepthMD->isValid()) + { + // Print the scale black background + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_QUADS); + glColor4f(0, 0, 0, 0.7); + glVertex2i(0, WIN_SIZE_Y); // lower left + glVertex2i(WIN_SIZE_X, WIN_SIZE_Y); + glVertex2i(WIN_SIZE_X, WIN_SIZE_Y - 135); + glVertex2i(0, WIN_SIZE_Y - 135); + glEnd(); + + glDisable(GL_BLEND); + + // set a large point size (for the scale) + glPointSize(15); + + // Print the scale data + glBegin(GL_POINTS); + for (int i=0; i 0.004) && (fNewColor < 0.996)) + { + glColor3f(fNewColor, fNewColor, 0); + glVertex3f(((i/10)*2), WIN_SIZE_Y - 23, 1); + } + } + glEnd(); + + // Print the pointer scale data + if (pPointer != NULL) + { + // make sure pointer in on a depth pixel (take in mind cropping might be in place) + IntPair pointerInDepth = *pPointer; + pointerInDepth.X -= pDepthMD->getCropOriginX(); + pointerInDepth.Y -= pDepthMD->getCropOriginY(); + + if (pointerInDepth.X < (int)pDepthMD->getWidth() && + pointerInDepth.X >= 0 && + pointerInDepth.Y < (int)pDepthMD->getHeight() && + pointerInDepth.Y >= 0) + { + nPointerValue = ((openni::DepthPixel*)(pDepthMD->getData()))[pointerInDepth.Y*pDepthMD->getWidth()+pointerInDepth.X]; + + glBegin(GL_POINTS); + glColor3f(1,0,0); + glVertex3f(10 + ((nPointerValue/10)*2), WIN_SIZE_Y - 70, 1); + glEnd(); + } + } + + // Print the scale texts + for (int i=0; iGetInfo().GetInstanceName()*/, pDepthMD->getFrameIndex(), (double)pDepthMD->getTimestamp()/dTimestampDivider); + } + + openni::VideoFrameRef* pImageMD = &getColorFrame(); + if (pImageMD->isValid()) + { + if (buf[0] != '\0') + { + xnOSStrAppend(buf, " | ", sizeof(buf)); + } + + xnOSStrFormat(buf + strlen(buf), (XnUInt32)(sizeof(buf) - strlen(buf)), &chars, "%s - Frame %4u, Timestamp %.3f", "Color", pImageMD->getFrameIndex(), (double)pImageMD->getTimestamp()/dTimestampDivider); + } + + openni::VideoFrameRef* pIRMD = &getIRFrame(); + if (pIRMD->isValid()) + { + if (buf[0] != '\0') + { + xnOSStrAppend(buf, " | ", sizeof(buf)); + } + + xnOSStrFormat(buf + strlen(buf), (XnUInt32)(sizeof(buf) - strlen(buf)), &chars, "%s - Frame %4u, Timestamp %.3f", "IR", pIRMD->getFrameIndex(), (double)pIRMD->getTimestamp()/dTimestampDivider); + } + + int nYLocation = WIN_SIZE_Y - 88; + glColor3f(1,0,0); + glRasterPos2i(10,nYLocation); + glPrintString(GLUT_BITMAP_HELVETICA_18, buf); + nYLocation -= 26; + + if (pPointer != NULL) + { + // Print the pointer text + XnUInt64 nCutOffMin = 0; + XnUInt64 nCutOffMax = (pDepthMD != NULL) ? g_fMaxDepth : 0; + + XnChar sPointerValue[100]; + if (nPointerValue != g_fMaxDepth) + { + xnOSStrFormat(sPointerValue, sizeof(sPointerValue), &chars, "%.1f", (float)nPointerValue/10); + } + else + { + xnOSStrFormat(sPointerValue, sizeof(sPointerValue), &chars, "-"); + } + + xnOSStrFormat(buf, sizeof(buf), &chars, "Pointer Value: %s (X:%d Y:%d) Cutoff: %llu-%llu.", + sPointerValue, pPointer->X, pPointer->Y, nCutOffMin, nCutOffMax); + + glRasterPos2i(10,nYLocation); + glPrintString(GLUT_BITMAP_HELVETICA_18, buf); + nYLocation -= 26; + } +} + +void drawCenteredMessage(void* font, int y, const char* message, float fRed, float fGreen, float fBlue) +{ + const XnUInt32 nMaxLines = 5; + XnChar buf[512]; + XnChar* aLines[nMaxLines]; + XnUInt32 anLinesWidths[nMaxLines]; + XnUInt32 nLine = 0; + XnUInt32 nLineLengthChars = 0; + XnInt32 nLineLengthPixels = 0; + XnInt32 nMaxLineLength = 0; + + aLines[0] = buf; + + // parse message to lines + const char* pChar = message; + for (;;) + { + if (*pChar == '\n' || *pChar == '\0') + { + if (nLineLengthChars > 0) + { + aLines[nLine][nLineLengthChars++] = '\0'; + aLines[nLine+1] = &aLines[nLine][nLineLengthChars]; + anLinesWidths[nLine] = nLineLengthPixels; + nLine++; + if (nLineLengthPixels > nMaxLineLength) + { + nMaxLineLength = nLineLengthPixels; + } + nLineLengthPixels = 0; + nLineLengthChars = 0; + } + + if (nLine >= nMaxLines || *pChar == '\0') + { + break; + } + } + else + { + aLines[nLine][nLineLengthChars++] = *pChar; + nLineLengthPixels += glutBitmapWidth(font, *pChar); + } + pChar++; + } + + XnUInt32 nHeight = 26; + int nXLocation = xnl::Math::Max(0, (WIN_SIZE_X - nMaxLineLength) / 2); + int nYLocation = y; + + // Draw black background + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_QUADS); + glColor4f(0, 0, 0, 0.6); + glVertex2i(nXLocation - 5, nYLocation - nHeight - 5); + glVertex2i(nXLocation + nMaxLineLength + 5, nYLocation - nHeight - 5); + glVertex2i(nXLocation + nMaxLineLength + 5, nYLocation + nHeight * nLine + 5); + glVertex2i(nXLocation - 5, nYLocation + nHeight * nLine + 5); + glEnd(); + + glDisable(GL_BLEND); + + // show message + glColor3f(fRed, fGreen, fBlue); + for (XnUInt32 i = 0; i < nLine; ++i) + { + glRasterPos2i(nXLocation + (nMaxLineLength - anLinesWidths[i])/2, nYLocation + i * nHeight); + glPrintString(font, aLines[i]); + } +} + +void drawUserMessage() +{ + const float fMessageTypeColors[NUM_DRAW_MESSAGE_TYPES][3] = + { + { 0, 1, 0 }, /*NOTIFICATION_MESSAGE*/ + { 1, 1, 0 }, /*WARNING_MESSAGE*/ + { 1, 0, 0 }, /*ERROR_MESSAGE*/ + { 1, 0, 0 }, /*FATAL_MESSAGE*/ + }; + + if (isInKeyboardInputMode()) + { + drawCenteredMessage(GLUT_BITMAP_TIMES_ROMAN_24, WIN_SIZE_Y * 4 / 5, getCurrentKeyboardInputMessage(), 0, 1, 0); + } + + static XnUInt64 nStartShowMessage = 0; + + if (g_DrawConfig.bShowMessage) + { + g_DrawConfig.bShowMessage = false; + xnOSGetTimeStamp(&nStartShowMessage); + } + + XnUInt64 nNow; + xnOSGetTimeStamp(&nNow); + + if (nNow - nStartShowMessage < 3000) + { + drawCenteredMessage(GLUT_BITMAP_TIMES_ROMAN_24, WIN_SIZE_Y * 4 / 5, g_csUserMessage, + fMessageTypeColors[g_DrawConfig.messageType][0], + fMessageTypeColors[g_DrawConfig.messageType][1], + fMessageTypeColors[g_DrawConfig.messageType][2]); + } +} + +void printRecordingInfo() +{ + char csMessage[256]; + getCaptureMessage(csMessage); + + if (csMessage[0] != 0) + drawCenteredMessage(GLUT_BITMAP_TIMES_ROMAN_24, 30, csMessage, 1, 0, 0); + + XnUInt32 nWritten; + xnOSStrFormat(csMessage, sizeof(csMessage), &nWritten, + "Image registration is %s. Capture Formats - Depth: %s | Image: %s | IR: %s", + getDevice().getImageRegistrationMode() == openni::IMAGE_REGISTRATION_OFF ? "off " : "on", + captureGetDepthFormatName(), + captureGetColorFormatName(), + captureGetIRFormatName()); + + drawCenteredMessage(GLUT_BITMAP_HELVETICA_12, WIN_SIZE_Y - 3, csMessage, 0, 1, 0); +} + +void printHelpGroup(int nXLocation, int* pnYLocation, const char* csGroup) +{ + int nYLocation = *pnYLocation; + + int aSpecialKeys[20]; + unsigned char aKeys[20]; + const char* aDescs[20]; + int nSpecialCount,nCount; + + getGroupItems(csGroup, aSpecialKeys, aKeys, aDescs, &nSpecialCount, &nCount); + + glColor3f(0, 1, 0); + glRasterPos2i(nXLocation, nYLocation); + glPrintString(GLUT_BITMAP_TIMES_ROMAN_24, csGroup); + nYLocation += 30; + + for (int i = 0; i < (nSpecialCount + nCount); ++i, nYLocation += 22) + { + char buf[256]; + XnUInt32 nWritten; + if(i < nSpecialCount) + { + switch (aSpecialKeys[i]) + { + case GLUT_KEY_LEFT: + xnOSStrFormat(buf, sizeof(buf), &nWritten, "Left"); break; + case GLUT_KEY_RIGHT: + xnOSStrFormat(buf, sizeof(buf), &nWritten, "Right"); break; + case GLUT_KEY_UP: + xnOSStrFormat(buf, sizeof(buf), &nWritten, "Up"); break; + case GLUT_KEY_DOWN: + xnOSStrFormat(buf, sizeof(buf), &nWritten, "Down"); break; + default: + xnOSStrFormat(buf, sizeof(buf), &nWritten, "[0x%2x]", aSpecialKeys[i]); break; + } + } + else + { + int j = i - nSpecialCount; + switch (aKeys[j]) + { + case 27: + xnOSStrFormat(buf, sizeof(buf), &nWritten, "Esc"); break; + case ' ': + xnOSStrFormat(buf, sizeof(buf), &nWritten, "Space"); break; + default: + xnOSStrFormat(buf, sizeof(buf), &nWritten, "%c", aKeys[j]); + break; + } + } + + glColor3f(1, 0, 0); + glRasterPos2i(nXLocation, nYLocation); + glPrintString(GLUT_BITMAP_HELVETICA_18, buf); + + glRasterPos2i(nXLocation + 50, nYLocation); + glPrintString(GLUT_BITMAP_HELVETICA_18, aDescs[i]); + } + + *pnYLocation = nYLocation + 20; +} + +void drawErrorState() +{ + // place a black rect on entire screen + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_QUADS); + glColor4f(0, 0, 0, 0.8); + glVertex2i(0, 0); + glVertex2i(WIN_SIZE_X, 0); + glVertex2i(WIN_SIZE_X, WIN_SIZE_Y); + glVertex2i(0, WIN_SIZE_Y); + glEnd(); + glDisable(GL_BLEND); + + int nYLocation = WIN_SIZE_Y/2 - 30; + + drawCenteredMessage(GLUT_BITMAP_TIMES_ROMAN_24, nYLocation, "ERROR!", 1, 0, 0); + nYLocation += 40; + drawCenteredMessage(GLUT_BITMAP_TIMES_ROMAN_24, nYLocation, g_DrawConfig.strErrorState, 1, 0, 0); +} + +void drawHelpScreen() +{ + int nXStartLocation = WIN_SIZE_X/8; + int nYStartLocation = WIN_SIZE_Y/5; + int nXEndLocation = WIN_SIZE_X*7/8; + int nYEndLocation = WIN_SIZE_Y*4/5; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_QUADS); + glColor4f(0, 0, 0, 0.8); + glVertex2i(nXStartLocation, nYStartLocation); + glVertex2i(nXStartLocation, nYEndLocation); + glVertex2i(nXEndLocation, nYEndLocation); + glVertex2i(nXEndLocation, nYStartLocation); + glEnd(); + + glDisable(GL_BLEND); + + // set color to red + glColor3f(1, 0, 0); + + // leave some margins + nYStartLocation += 30; + nXStartLocation += 30; + + // print left pane + int nXLocation = nXStartLocation; + int nYLocation = nYStartLocation; + printHelpGroup(nXLocation, &nYLocation, KEYBOARD_GROUP_PRESETS); + printHelpGroup(nXLocation, &nYLocation, KEYBOARD_GROUP_DISPLAY); + printHelpGroup(nXLocation, &nYLocation, KEYBOARD_GROUP_DEVICE); + + // print right pane + nXLocation = WIN_SIZE_X/2; + nYLocation = nYStartLocation; + printHelpGroup(nXLocation, &nYLocation, KEYBOARD_GROUP_PLAYER); + printHelpGroup(nXLocation, &nYLocation, KEYBOARD_GROUP_CAPTURE); + printHelpGroup(nXLocation, &nYLocation, KEYBOARD_GROUP_GENERAL); +} + +void drawUserInput(bool bCursor) +{ + if (bCursor) + { + // draw cursor + IntPair cursor = g_DrawUserInput.Cursor; + glPointSize(1); + glBegin(GL_POINTS); + glColor3f(1,0,0); + glVertex2i(cursor.X, cursor.Y); + + // upper left marker + glVertex2i(cursor.X - 2, cursor.Y - 2); + glVertex2i(cursor.X - 2, cursor.Y - 1); + glVertex2i(cursor.X - 1, cursor.Y - 2); + + // bottom left marker + glVertex2i(cursor.X - 2, cursor.Y + 2); + glVertex2i(cursor.X - 2, cursor.Y + 1); + glVertex2i(cursor.X - 1, cursor.Y + 2); + + // upper right marker + glVertex2i(cursor.X + 2, cursor.Y - 2); + glVertex2i(cursor.X + 2, cursor.Y - 1); + glVertex2i(cursor.X + 1, cursor.Y - 2); + + // lower right marker + glVertex2i(cursor.X + 2, cursor.Y + 2); + glVertex2i(cursor.X + 2, cursor.Y + 1); + glVertex2i(cursor.X + 1, cursor.Y + 2); + + glEnd(); + } + + // draw selection frame + if (g_DrawUserInput.State == SELECTION_ACTIVE) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_QUADS); + glColor4f(1, 0, 0, 0.5); + glVertex2i(g_DrawUserInput.Rect.uLeft, g_DrawUserInput.Rect.uTop); // Upper left + glVertex2i(g_DrawUserInput.Rect.uRight, g_DrawUserInput.Rect.uTop); // upper right + glVertex2i(g_DrawUserInput.Rect.uRight, g_DrawUserInput.Rect.uBottom); // lower right + glVertex2i(g_DrawUserInput.Rect.uLeft, g_DrawUserInput.Rect.uBottom); // lower left + glEnd(); + + glDisable(GL_BLEND); + } +} + +void fixLocation(IntRect* pLocation, int xRes, int yRes) +{ + double resRatio = (double)xRes / yRes; + + double locationRatio = (pLocation->uRight - pLocation->uLeft) / (pLocation->uTop - pLocation->uBottom); + + if (locationRatio > resRatio) + { + // location is wider. use height as reference. + double width = (pLocation->uTop - pLocation->uBottom) * resRatio; + pLocation->uRight = (pLocation->uLeft + width); + } + else if (locationRatio < resRatio) + { + // res is wider. use width as reference. + double height = (pLocation->uRight - pLocation->uLeft) / resRatio; + pLocation->uTop = (pLocation->uBottom + height); + } +} + +bool isPointInRect(IntPair point, IntRect* pRect) +{ + return (point.X >= pRect->uLeft && point.X <= pRect->uRight && + point.Y >= pRect->uBottom && point.Y <= pRect->uTop); +} + +void drawPlaybackSpeed() +{ +// XnDouble dSpeed = getPlaybackSpeed(); +// if (dSpeed != 1.0) +// { +// XnChar strSpeed[30]; +// int len = sprintf(strSpeed, "x%g", dSpeed); +// int width = 0; +// for (int i = 0; i < len; ++i) +// width += glutBitmapWidth(GLUT_BITMAP_TIMES_ROMAN_24, strSpeed[i]); +// +// glColor3f(0, 1, 0); +// glRasterPos2i(WIN_SIZE_X - width - 3, 30); +// glPrintString(GLUT_BITMAP_TIMES_ROMAN_24, strSpeed); +// } +} + +void drawFrame() +{ + // calculate locations + g_DrawConfig.DepthLocation.uBottom = 0; + g_DrawConfig.DepthLocation.uTop = WIN_SIZE_Y - 1; + g_DrawConfig.DepthLocation.uLeft = 0; + g_DrawConfig.DepthLocation.uRight = WIN_SIZE_X - 1; + + g_DrawConfig.ColorLocation.uBottom = 0; + g_DrawConfig.ColorLocation.uTop = WIN_SIZE_Y - 1; + g_DrawConfig.ColorLocation.uLeft = 0; + g_DrawConfig.ColorLocation.uRight = WIN_SIZE_X - 1; + + if (g_DrawConfig.Streams.ScreenArrangement == SIDE_BY_SIDE) + { + g_DrawConfig.DepthLocation.uTop = WIN_SIZE_Y / 2 - 1; + g_DrawConfig.DepthLocation.uRight = WIN_SIZE_X / 2 - 1; + g_DrawConfig.ColorLocation.uTop = WIN_SIZE_Y / 2 - 1; + g_DrawConfig.ColorLocation.uLeft = WIN_SIZE_X / 2; + } + + // Texture map init + openni::VideoFrameRef* pDepthMD = &getDepthFrame(); + if (isDepthOn() && pDepthMD->isValid()) + { + int maxDepth = 0; + maxDepth = getDepthStream().getMaxPixelValue(); + g_fMaxDepth = maxDepth; + + TextureMapInit(&g_texDepth, pDepthMD->getVideoMode().getResolutionX(), pDepthMD->getVideoMode().getResolutionY(), 4, pDepthMD->getWidth(), pDepthMD->getHeight()); + fixLocation(&g_DrawConfig.DepthLocation, pDepthMD->getVideoMode().getResolutionX(), pDepthMD->getVideoMode().getResolutionY()); + } + + openni::VideoFrameRef* pImageMD = NULL; + + if (isColorOn()) + { + pImageMD = &getColorFrame(); + } + else if (isIROn()) + { + pImageMD = &getIRFrame(); + } + + if (pImageMD != NULL && pImageMD->isValid()) + { + TextureMapInit(&g_texColor, pImageMD->getVideoMode().getResolutionX(), pImageMD->getVideoMode().getResolutionY(), 4, pImageMD->getWidth(), pImageMD->getHeight()); + fixLocation(&g_DrawConfig.ColorLocation, pImageMD->getVideoMode().getResolutionX(), pImageMD->getVideoMode().getResolutionY()); + } + + // check if pointer is over a map + bool bOverDepth = (pDepthMD != NULL && pDepthMD->isValid()) && isPointInRect(g_DrawUserInput.Cursor, &g_DrawConfig.DepthLocation); + bool bOverImage = (pImageMD != NULL && pImageMD->isValid()) && isPointInRect(g_DrawUserInput.Cursor, &g_DrawConfig.ColorLocation); + bool bDrawDepthPointer = false; + bool bDrawImagePointer = false; + int imagePointerRed = 255; + int imagePointerGreen = 0; + int imagePointerBlue = 0; + + IntPair pointerInDepth = {0,0}; + IntPair pointerInColor = {0,0}; + + if (bOverImage) + { + pointerInColor.X = (double)(g_DrawUserInput.Cursor.X - g_DrawConfig.ColorLocation.uLeft) / (g_DrawConfig.ColorLocation.uRight - g_DrawConfig.ColorLocation.uLeft + 1) * pImageMD->getVideoMode().getResolutionX(); + pointerInColor.Y = (double)(g_DrawUserInput.Cursor.Y - g_DrawConfig.ColorLocation.uBottom) / (g_DrawConfig.ColorLocation.uTop - g_DrawConfig.ColorLocation.uBottom + 1) * pImageMD->getVideoMode().getResolutionY(); + bDrawImagePointer = true; + } + + if (bOverDepth) + { + pointerInDepth.X = (double)(g_DrawUserInput.Cursor.X - g_DrawConfig.DepthLocation.uLeft) / (g_DrawConfig.DepthLocation.uRight - g_DrawConfig.DepthLocation.uLeft + 1) * pDepthMD->getVideoMode().getResolutionX(); + pointerInDepth.Y = (double)(g_DrawUserInput.Cursor.Y - g_DrawConfig.DepthLocation.uBottom) / (g_DrawConfig.DepthLocation.uTop - g_DrawConfig.DepthLocation.uBottom + 1) * pDepthMD->getVideoMode().getResolutionY(); + bDrawDepthPointer = true; + + if (!bOverImage && g_DrawConfig.bShowPointer && + pointerInDepth.X >= pDepthMD->getCropOriginX() && pointerInDepth.X < (pDepthMD->getCropOriginX() + pDepthMD->getWidth()) && + pointerInDepth.Y >= pDepthMD->getCropOriginY() && pointerInDepth.Y < (pDepthMD->getCropOriginY() + pDepthMD->getHeight())) + { + + // try to translate depth pixel to image + openni::DepthPixel* pDepthPixels = (openni::DepthPixel*)pDepthMD->getData(); + openni::DepthPixel pointerDepth = pDepthPixels[(pointerInDepth.Y - pDepthMD->getCropOriginY()) * pDepthMD->getWidth() + (pointerInDepth.X - pDepthMD->getCropOriginX())]; + if (convertDepthPointToColor(pointerInDepth.X, pointerInDepth.Y, pointerDepth, &pointerInColor.X, &pointerInColor.Y)) + { + bDrawImagePointer = true; + imagePointerRed = 0; + imagePointerGreen = 0; + imagePointerBlue = 255; + } + } + } + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + // Setup the opengl env for fixed location view + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0,WIN_SIZE_X,WIN_SIZE_Y,0,-1.0,1.0); + glDisable(GL_DEPTH_TEST); + + if (g_DrawConfig.Streams.Depth.Coloring == CYCLIC_RAINBOW_HISTOGRAM || g_DrawConfig.Streams.Depth.Coloring == LINEAR_HISTOGRAM || g_DrawConfig.bShowPointer) + calculateHistogram(); + + drawColor(&g_DrawConfig.ColorLocation, bDrawImagePointer ? &pointerInColor : NULL, imagePointerRed, imagePointerGreen, imagePointerBlue); + + drawDepth(&g_DrawConfig.DepthLocation, bDrawDepthPointer ? &pointerInDepth : NULL); + + printRecordingInfo(); + + if (g_DrawConfig.bShowPointer) + drawPointerMode(bOverDepth ? &pointerInDepth : NULL); + + drawUserInput(!bOverDepth && !bOverImage); + + drawUserMessage(); + drawPlaybackSpeed(); + + if (g_DrawConfig.strErrorState[0] != '\0') + drawErrorState(); + + if (g_DrawConfig.bHelp) + drawHelpScreen(); + + glutSwapBuffers(); +} + +void setDepthDrawing(int nColoring) +{ + g_DrawConfig.Streams.Depth.Coloring = (DepthDrawColoringType)nColoring; +} + +void setColorDrawing(int nColoring) +{ + g_DrawConfig.Streams.Color.Coloring = (ColorDrawColoringType)nColoring; +} diff --git a/Source/Tools/NiViewer/Draw.h b/Source/Tools/NiViewer/Draw.h new file mode 100644 index 0000000..a808bcc --- /dev/null +++ b/Source/Tools/NiViewer/Draw.h @@ -0,0 +1,108 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __DRAW_H__ +#define __DRAW_H__ + +// -------------------------------- +// Includes +// -------------------------------- +#define _CRT_SECURE_NO_DEPRECATE 1 +#include + +// -------------------------------- +// Defines +// -------------------------------- +#define WIN_SIZE_X 1280 +#define WIN_SIZE_Y 1024 + +#define PRESET_COUNT 13 + +// -------------------------------- +// Types +// -------------------------------- +typedef enum +{ + OVERLAY, + SIDE_BY_SIDE +} ScreenArrangementType; + +typedef enum +{ + DEPTH_OFF, + LINEAR_HISTOGRAM, + PSYCHEDELIC, + PSYCHEDELIC_SHADES, + RAINBOW, + CYCLIC_RAINBOW, + CYCLIC_RAINBOW_HISTOGRAM, + STANDARD_DEVIATION, + NUM_OF_DEPTH_DRAW_TYPES, +} DepthDrawColoringType; + +extern const char* g_DepthDrawColoring[NUM_OF_DEPTH_DRAW_TYPES]; + +typedef enum +{ + COLOR_OFF, + COLOR_NORMAL, + DEPTH_MASKED_COLOR, + NUM_OF_COLOR_DRAW_TYPES, +} ColorDrawColoringType; + +extern const char* g_ColorDrawColoring[NUM_OF_COLOR_DRAW_TYPES]; + +typedef struct +{ + DepthDrawColoringType Coloring; + float fTransparency; +} DepthDrawConfig; + +typedef struct +{ + ColorDrawColoringType Coloring; +} ImageDrawConfig; + +typedef struct +{ + DepthDrawConfig Depth; + ImageDrawConfig Color; + ScreenArrangementType ScreenArrangement; +} StreamsDrawConfig; + +// -------------------------------- +// Drawing functions +// -------------------------------- +void drawInit(); +void displayMessage(const char* csFormat, ...); +void displayError(const char* csFormat, ...); +void setPreset(int preset); +const char* getPresetName(int preset); +void setScreenLayout(int layout); +void toggleFullScreen(int); +void togglePointerMode(int); +void toggleHelpScreen(int); +void drawFrame(); +void windowReshaped(int width, int height); +void setDepthDrawing(int nColoring); +void setColorDrawing(int nColoring); +void setErrorState(const char* strFormat, ...); + +#endif //__DRAW_H__ diff --git a/Source/Tools/NiViewer/Keyboard.cpp b/Source/Tools/NiViewer/Keyboard.cpp new file mode 100644 index 0000000..0678bf7 --- /dev/null +++ b/Source/Tools/NiViewer/Keyboard.cpp @@ -0,0 +1,231 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Includes +// -------------------------------- +#include "Keyboard.h" +#include +#include + +// -------------------------------- +// Types +// -------------------------------- +typedef struct XnKeyboardAction +{ + char key; + const char* csDescription; + ActionFunc pCallbackFunc; + int nCallbackArg; +} XnKeyboardAction; + +typedef struct XnKeyboardGroup +{ + const char* csName; + int nFirst; + int nLast; + int nSpecialFirst; + int nSpecialLast; +} XnKeyboardGroup; + +// -------------------------------- +// Global Variables +// -------------------------------- +XnKeyboardAction g_KeyboardMap[500]; +XnKeyboardAction g_KeyboardSpecialMap[100]; +XnKeyboardGroup g_Groups[20]; +int g_nRegisteredKeys = 0; +int g_nRegisteredSpecialKeys = 0; +int g_nRegisteredGroups = 0; + +static bool g_bUserInput = false; +static bool g_bUserInputNumbersOnly = false; +static char g_strUserInput[1024]; +static int g_userInputStartPos = 0; +static KeyboardInputEnded g_userInputCallback; + +// -------------------------------- +// Code +// -------------------------------- +void startKeyboardMap() +{ + g_nRegisteredKeys = 0; +} + +void startKeyboardGroup(const char* csName) +{ + g_Groups[g_nRegisteredGroups].csName = csName; + g_Groups[g_nRegisteredGroups].nFirst = g_nRegisteredKeys; + g_Groups[g_nRegisteredGroups].nSpecialFirst = g_nRegisteredSpecialKeys; +} + +void registerKey(unsigned char key, const char* Description, ActionFunc func, int arg) +{ + XnKeyboardAction* pKey = &g_KeyboardMap[g_nRegisteredKeys++]; + pKey->key = key; + pKey->csDescription = Description; + pKey->pCallbackFunc = func; + pKey->nCallbackArg = arg; +} + +void registerSpecialKey(char key, const char* Description, ActionFunc func, int arg) +{ + XnKeyboardAction* pKey = &g_KeyboardSpecialMap[g_nRegisteredSpecialKeys++]; + pKey->key = key; + pKey->csDescription = Description; + pKey->pCallbackFunc = func; + pKey->nCallbackArg = arg; +} + +void endKeyboardGroup() +{ + g_Groups[g_nRegisteredGroups].nLast = g_nRegisteredKeys; + g_Groups[g_nRegisteredGroups].nSpecialLast = g_nRegisteredSpecialKeys; + g_nRegisteredGroups++; +} + +void endKeyboardMap() +{ + +} + +char getRegisteredKey(ActionFunc func, int arg) +{ + for (int i = 0; i < g_nRegisteredKeys; ++i) + { + if (g_KeyboardMap[i].pCallbackFunc == func && g_KeyboardMap[i].nCallbackArg == arg) + return g_KeyboardMap[i].key; + } + + return 0; +} + +int getRegisteredSpecialKey(ActionFunc func, int arg) +{ + for (int i = 0; i < g_nRegisteredKeys; ++i) + { + if (g_KeyboardSpecialMap[i].pCallbackFunc == func && g_KeyboardSpecialMap[i].nCallbackArg == arg) + return g_KeyboardSpecialMap[i].key; + } + + return 0; +} + +void handleKey(unsigned char k) +{ + if (g_bUserInput) + { + if (k == 13 || k == 27) // ENTER or ESC + { + if (g_userInputCallback != NULL) + { + g_userInputCallback(k == 13, &g_strUserInput[g_userInputStartPos]); + } + + g_bUserInput = false; + } + + if (g_bUserInputNumbersOnly && (k < '0' || k > '9')) + { + // ignore + return; + } + + int len = (int)strlen(g_strUserInput); + g_strUserInput[len] = k; + g_strUserInput[++len] = '\0'; + + return; + } + + for (int i = 0; i < g_nRegisteredKeys; ++i) + { + if (k == g_KeyboardMap[i].key) + { + g_KeyboardMap[i].pCallbackFunc(g_KeyboardMap[i].nCallbackArg); + return; + } + } +} + +void handleSpecialKey(int k) +{ + for (int i = 0; i < g_nRegisteredSpecialKeys; ++i) + { + if (k == g_KeyboardSpecialMap[i].key) + { + g_KeyboardSpecialMap[i].pCallbackFunc(g_KeyboardSpecialMap[i].nCallbackArg); + return; + } + } +} + +int findGroup(const char* csGroupName) +{ + for (int i = 0; i < g_nRegisteredGroups; ++i) + { + if (strcmp(g_Groups[i].csName, csGroupName) == 0) + return i; + } + + return -1; +} + +void getGroupItems(const char* csGroupName, int *pSpecialKeys, unsigned char* pKeys, const char** pDescs, int* pSpecialCount, int* pCount) +{ + // find group + int nGroup = findGroup(csGroupName); + + int nCount = 0; + for (int nEntry = g_Groups[nGroup].nSpecialFirst; nEntry < g_Groups[nGroup].nSpecialLast; nEntry++, nCount++) + { + pSpecialKeys[nCount] = g_KeyboardSpecialMap[nEntry].key; + pDescs[nCount] = g_KeyboardSpecialMap[nEntry].csDescription; + } + *pSpecialCount = nCount; + + nCount = 0; + for (int nEntry = g_Groups[nGroup].nFirst; nEntry < g_Groups[nGroup].nLast; nEntry++, nCount++) + { + pKeys[nCount] = g_KeyboardMap[nEntry].key; + pDescs[nCount + *pSpecialCount] = g_KeyboardMap[nEntry].csDescription; + } + + *pCount = nCount; +} + +void startKeyboardInputMode(const char* message, bool numbersOnly, KeyboardInputEnded callback) +{ + g_bUserInput = true; + g_bUserInputNumbersOnly = numbersOnly; + xnOSStrCopy(g_strUserInput, message, sizeof(g_strUserInput)); + g_userInputStartPos = (int)strlen(message); + g_userInputCallback = callback; +} + +const char* getCurrentKeyboardInputMessage() +{ + return g_strUserInput; +} + +bool isInKeyboardInputMode() +{ + return g_bUserInput; +} diff --git a/Source/Tools/NiViewer/Keyboard.h b/Source/Tools/NiViewer/Keyboard.h new file mode 100644 index 0000000..17f2f3b --- /dev/null +++ b/Source/Tools/NiViewer/Keyboard.h @@ -0,0 +1,61 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __KEYBOARD_H__ +#define __KEYBOARD_H__ + +// -------------------------------- +// Defines +// -------------------------------- +#define KEYBOARD_GROUP_PRESETS "Presets" +#define KEYBOARD_GROUP_DISPLAY "Display" +#define KEYBOARD_GROUP_GENERAL "General" +#define KEYBOARD_GROUP_DEVICE "Device" +#define KEYBOARD_GROUP_CAPTURE "Capture" +#define KEYBOARD_GROUP_PLAYER "Player" + +// -------------------------------- +// Types +// -------------------------------- +typedef void (*ActionFunc)(int); +typedef void (*KeyboardInputEnded)(bool ok, const char* userInput); + +// -------------------------------- +// Function Declarations +// -------------------------------- +void startKeyboardMap(); +void startKeyboardGroup(const char* csName); +void registerKey(unsigned char key, const char* Description, ActionFunc func, int arg); +void registerSpecialKey(char key, const char* Description, ActionFunc func, int arg); +void endKeyboardGroup(); +void endKeyboardMap(); +char getRegisteredKey(ActionFunc func, int arg); +int getRegisteredSpecialKey(ActionFunc func, int arg); +void handleKey(unsigned char key); +void handleSpecialKey(int k); + +void getGroupItems(const char* csGroupName, int *pSpecialKeys, unsigned char* pKeys, const char** pDescs, int* pSpecialCount, int* pCount); + +void startKeyboardInputMode(const char* message, bool numbersOnly, KeyboardInputEnded callback); +bool isInKeyboardInputMode(); +const char* getCurrentKeyboardInputMessage(); + + +#endif //__KEYBOARD_H__ diff --git a/Source/Tools/NiViewer/Makefile b/Source/Tools/NiViewer/Makefile new file mode 100644 index 0000000..1eba049 --- /dev/null +++ b/Source/Tools/NiViewer/Makefile @@ -0,0 +1,35 @@ +include ../../../ThirdParty/PSCommon/BuildSystem/CommonDefs.mak + +BIN_DIR = ../../../Bin + +INC_DIRS = \ + ../../../Include \ + ../../../ThirdParty/PSCommon/XnLib/Include \ + ../../../ThirdParty/GL/ + +SRC_FILES = \ + Device.cpp \ + Draw.cpp \ + Keyboard.cpp \ + Menu.cpp \ + MouseInput.cpp \ + NiViewer.cpp \ + Statistics.cpp \ + Capture.cpp + +ifeq ("$(OSTYPE)","Darwin") + CFLAGS += -DMACOS + LDFLAGS += -framework OpenGL -framework GLUT +else + CFLAGS += -DUNIX -DGLX_GLXEXT_LEGACY + USED_LIBS += glut GL +endif + +LIB_DIRS += ../../../ThirdParty/PSCommon/XnLib/Bin/$(PLATFORM)-$(CFG) +USED_LIBS += OpenNI2 XnLib + +EXE_NAME = NiViewer + +CFLAGS += -Wall + +include ../../../ThirdParty/PSCommon/BuildSystem/CommonCppMakefile diff --git a/Source/Tools/NiViewer/Menu.cpp b/Source/Tools/NiViewer/Menu.cpp new file mode 100644 index 0000000..5cd05e8 --- /dev/null +++ b/Source/Tools/NiViewer/Menu.cpp @@ -0,0 +1,149 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Includes +// -------------------------------- +#include +#include "Menu.h" +#if (XN_PLATFORM == XN_PLATFORM_MACOSX) + #include +#else + #include +#endif +#include +#include + +// -------------------------------- +// Defines +// -------------------------------- +#define XN_MAX_MENU_ENTRIES 300 +#define XN_MAX_MENU_ENTRY_NAME_LEN 70 +#define XN_MAX_MENU_DEPTH 20 + +// -------------------------------- +// Types +// -------------------------------- +struct XnMenuBuilder +{ + int aMenuPath[XN_MAX_MENU_DEPTH]; + int nMenuPathIndex; + int nCurrMenuItem; +} g_MenuBuilder; + +typedef struct XnMenuEntry +{ + char csName[XN_MAX_MENU_ENTRY_NAME_LEN]; + ActionFunc pCallbackFunc; + int nCallbackArg; +} XnMenuEntry; + +// -------------------------------- +// Global Variables +// -------------------------------- +/* Holds a list of all menu entries and their callbacks. */ +XnMenuEntry g_MenuEntries[XN_MAX_MENU_ENTRIES]; + +// -------------------------------- +// Code +// -------------------------------- +void mainMenuCallback(int nMenuID) +{ + g_MenuEntries[nMenuID].pCallbackFunc(g_MenuEntries[nMenuID].nCallbackArg); +} + +void startMenu() +{ + g_MenuBuilder.aMenuPath[0] = glutCreateMenu(mainMenuCallback); + g_MenuBuilder.nMenuPathIndex = 0; + g_MenuBuilder.nCurrMenuItem = 0; +} + +void startSubMenu(const char* csName) +{ + int nNewMenuID = glutCreateMenu(mainMenuCallback); + glutSetMenu(g_MenuBuilder.aMenuPath[g_MenuBuilder.nMenuPathIndex]); + glutAddSubMenu(csName, nNewMenuID); + + (g_MenuBuilder.nMenuPathIndex)++; + g_MenuBuilder.aMenuPath[g_MenuBuilder.nMenuPathIndex] = nNewMenuID; + glutSetMenu(g_MenuBuilder.aMenuPath[g_MenuBuilder.nMenuPathIndex]); +} + +void createMenuEntry(const char* csName, ActionFunc func, int arg) +{ + strcpy(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName, csName); + + int key = getRegisteredSpecialKey(func, arg); + if (key != 0) + { + switch(key) + { + case GLUT_KEY_LEFT: + strcat(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName + strlen(csName), " [Left]"); + break; + case GLUT_KEY_RIGHT: + strcat(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName + strlen(csName), " [Right]"); + break; + case GLUT_KEY_UP: + strcat(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName + strlen(csName), " [Up]"); + break; + case GLUT_KEY_DOWN: + strcat(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName + strlen(csName), " [Down]"); + break; + default: + break; + } + } + else + { + key = (int)getRegisteredKey(func, arg); + if (key != 0) + { + if (key == 27) + strcat(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName + strlen(csName), " [Esc]"); + else if (key == ' ') + strcat(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName + strlen(csName), " [Space]"); + else + sprintf(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName + strlen(csName), " [ %c ]", (char)key); + } + } + + g_MenuEntries[g_MenuBuilder.nCurrMenuItem].pCallbackFunc = func; + g_MenuEntries[g_MenuBuilder.nCurrMenuItem].nCallbackArg = arg; + + glutAddMenuEntry(g_MenuEntries[g_MenuBuilder.nCurrMenuItem].csName, g_MenuBuilder.nCurrMenuItem); + + (g_MenuBuilder.nCurrMenuItem)++; +} + +void endSubMenu() +{ + (g_MenuBuilder.nMenuPathIndex)--; + int nParentMenuID = g_MenuBuilder.aMenuPath[g_MenuBuilder.nMenuPathIndex]; + + glutSetMenu(nParentMenuID); +} + +void endMenu() +{ + glutSetMenu(g_MenuBuilder.aMenuPath[0]); + glutAttachMenu(GLUT_RIGHT_BUTTON); +} diff --git a/Source/Tools/NiViewer/Menu.h b/Source/Tools/NiViewer/Menu.h new file mode 100644 index 0000000..d65f4c8 --- /dev/null +++ b/Source/Tools/NiViewer/Menu.h @@ -0,0 +1,38 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +#ifndef __MENU_H__ +#define __MENU_H__ + +// -------------------------------- +// Includes +// -------------------------------- +#include "Keyboard.h" + +// -------------------------------- +// Function Declarations +// -------------------------------- +void startMenu(); +void startSubMenu(const char* csName); +void createMenuEntry(const char* csName, ActionFunc func, int arg); +void endSubMenu(); +void endMenu(); + +#endif //__MENU_H__ \ No newline at end of file diff --git a/Source/Tools/NiViewer/MouseInput.cpp b/Source/Tools/NiViewer/MouseInput.cpp new file mode 100644 index 0000000..c63a727 --- /dev/null +++ b/Source/Tools/NiViewer/MouseInput.cpp @@ -0,0 +1,107 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Includes +// -------------------------------- +#include "XnLib.h" +#include "XnMath.h" + +#include "MouseInput.h" +#if (XN_PLATFORM == XN_PLATFORM_MACOSX) + #include +#else + #include +#endif + +// -------------------------------- +// Types +// -------------------------------- +typedef struct MouseInput +{ + SelectionState nSelectionState; + IntPair StartSelection; + IntPair LastLocation; + SelectionRectangleChangedPtr pSelectionCallback; + CursorMovedPtr pCursorCallback; +} MouseInput; + +MouseInput g_MouseInput = {SELECTION_NONE, {0,0}, {0,0}, NULL, NULL}; + +void mouseInputCallSelection() +{ + if (g_MouseInput.pSelectionCallback != NULL) + { + IntRect selection; + selection.uBottom = xnl::Math::Min(g_MouseInput.StartSelection.Y, g_MouseInput.LastLocation.Y); + selection.uTop = xnl::Math::Max(g_MouseInput.StartSelection.Y, g_MouseInput.LastLocation.Y); + selection.uLeft = xnl::Math::Min(g_MouseInput.StartSelection.X, g_MouseInput.LastLocation.X); + selection.uRight = xnl::Math::Max(g_MouseInput.StartSelection.X, g_MouseInput.LastLocation.X); + + g_MouseInput.pSelectionCallback(g_MouseInput.nSelectionState, selection); + } +} + +void mouseInputMotion(int x, int y) +{ + g_MouseInput.LastLocation.X = x; + g_MouseInput.LastLocation.Y = y; + + if (g_MouseInput.pCursorCallback != NULL) + g_MouseInput.pCursorCallback(g_MouseInput.LastLocation); + + if (g_MouseInput.nSelectionState == SELECTION_ACTIVE) + mouseInputCallSelection(); +} + +void mouseInputButton(int button, int state, int x, int y) +{ + if (button == GLUT_LEFT_BUTTON) + { + if (state == GLUT_DOWN) + { + g_MouseInput.nSelectionState = SELECTION_ACTIVE; + g_MouseInput.StartSelection.X = x; + g_MouseInput.StartSelection.Y = y; + } + else if (state == GLUT_UP && g_MouseInput.nSelectionState == SELECTION_ACTIVE) + { + // this is only a selection if mouse has moved + if (x != g_MouseInput.StartSelection.X && y != g_MouseInput.StartSelection.Y) + { + g_MouseInput.nSelectionState = SELECTION_DONE; + mouseInputCallSelection(); + } + + g_MouseInput.nSelectionState = SELECTION_NONE; + } + } +} + +void mouseInputRegisterForSelectionRectangle(SelectionRectangleChangedPtr pFunc) +{ + g_MouseInput.pSelectionCallback = pFunc; +} + +void mouseInputRegisterForCursorMovement(CursorMovedPtr pFunc) +{ + g_MouseInput.pCursorCallback = pFunc; +} + diff --git a/Source/Tools/NiViewer/MouseInput.h b/Source/Tools/NiViewer/MouseInput.h new file mode 100644 index 0000000..b979015 --- /dev/null +++ b/Source/Tools/NiViewer/MouseInput.h @@ -0,0 +1,70 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// -------------------------------- +// Types +// -------------------------------- +typedef struct +{ + int X; + int Y; +} IntPair; + +typedef struct +{ + double X; + double Y; +} DoublePair; + +typedef struct +{ + double fBottom; + double fLeft; + double fTop; + double fRight; +} DoubleRect; + +typedef struct +{ + int uBottom; + int uLeft; + int uTop; + int uRight; +} IntRect; + +typedef enum SelectionState +{ + SELECTION_NONE, + SELECTION_ACTIVE, + SELECTION_DONE +} SelectionState; + +typedef void (*SelectionRectangleChangedPtr)(SelectionState state, IntRect selection); +typedef void (*CursorMovedPtr)(IntPair location); + +// -------------------------------- +// Functions +// -------------------------------- +void mouseInputMotion(int x, int y); +void mouseInputButton(int button, int state, int x, int y); +void mouseInputRegisterForSelectionRectangle(SelectionRectangleChangedPtr pFunc); +void mouseInputRegisterForCursorMovement(CursorMovedPtr pFunc); + + diff --git a/Source/Tools/NiViewer/NiViewer.cpp b/Source/Tools/NiViewer/NiViewer.cpp new file mode 100644 index 0000000..a0c2f06 --- /dev/null +++ b/Source/Tools/NiViewer/NiViewer.cpp @@ -0,0 +1,735 @@ +/***************************************************************************** +* * +* OpenNI 2.x Alpha * +* Copyright (C) 2012 PrimeSense Ltd. * +* * +* This file is part of OpenNI. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); * +* you may not use this file except in compliance with the License. * +* You may obtain a copy of the License at * +* * +* http://www.apache.org/licenses/LICENSE-2.0 * +* * +* Unless required by applicable law or agreed to in writing, software * +* distributed under the License is distributed on an "AS IS" BASIS, * +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +* See the License for the specific language governing permissions and * +* limitations under the License. * +* * +*****************************************************************************/ +// Application Usage: +// 1 - Switch to the depth map view mode. +// 2 - Switch to the psychedelic depth map view mode. In this mode each centimeter will have a difference color. +// 3 - Switch to the psychedelic depth map view mode. In this mode each centimeter will have a difference color and millimeters will have different shades. +// 4 - Switch to the depth map with rainbow colors view mode. +// 5 - Switch to the depth map with RGB registration view mode. +// 6 - Switch to the depth map with RGB registration view mode and a background image. +// 7 - Switch to the side by side of depth and color view mode. +// 8 - Switch to the color map with RGB registration view mode. +// 9 - Switch to the color map with RGB registration view mode. In this mode the depth will be semi transparent. +// 0 - Switch to the color map with RGB registration view mode. In this mode the depth will be semi transparent and use rainbow colors. +// - - Switch to the color map with RGB registration view mode. In this mode the depth will be semi transparent and use depth values color codes. +// = - Switch to the color map only view mode. +// p - Show the laser pointer and the cutoff parameters. +// m - Enable/Disable the mirror mode. +// q - Decrease the minimum depth cutoff by 1. +// Q - Decrease the minimum depth cutoff by 50. +// w - Increase the minimum depth cutoff by 1. +// W - Increase the minimum depth cutoff by 50. +// e - Decrease the maximum depth cutoff by 1. +// E - Decrease the maximum depth cutoff by 50. +// r - Increase the maximum depth cutoff by 1. +// R - Increase the maximum depth cutoff by 50. +// ESC - exit. + +// -------------------------------- +// Includes +// -------------------------------- +// #include +// +// #if (XN_PLATFORM == XN_PLATFORM_LINUX_X86 || XN_PLATFORM == XN_PLATFORM_LINUX_ARM) +// #define UNIX +// #define GLX_GLXEXT_LEGACY +// #endif +// +// #if (XN_PLATFORM == XN_PLATFORM_MACOSX) +// #define MACOS +// #endif + +// Undeprecate CRT functions +#ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE 1 +#endif + + +#include "Device.h" +#include "OpenNI.h" +#include "XnLib.h" + +#define GLH_EXT_SINGLE_FILE +#if (ONI_PLATFORM == ONI_PLATFORM_WIN32) +#pragma warning(push, 3) +#endif +#include +#include +#if (ONI_PLATFORM == ONI_PLATFORM_WIN32) +#pragma warning(pop) +#endif + +using namespace glh; + +// #include +#include "Capture.h" +#include "Draw.h" +#include "Keyboard.h" +#include "Menu.h" +#include "MouseInput.h" + +#if (ONI_PLATFORM == ONI_PLATFORM_WIN32) + #include + #include +#elif (ONI_PLATFORM == ONI_PLATFORM_LINUX_X86 || ONI_PLATFORM == ONI_PLATFORM_LINUX_ARM || ONI_PLATFORM == ONI_PLATFORM_MACOSX) + #define _getch() getchar() +#endif + +// -------------------------------- +// Defines +// -------------------------------- +#define SAMPLE_XML_PATH "../../../../Data/SamplesConfig.xml" + +// -------------------------------- +// Types +// -------------------------------- +enum { + ERR_OK, + ERR_USAGE, + ERR_DEVICE, + ERR_UNKNOWN +}; + +// -------------------------------- +// Global Variables +// -------------------------------- +/* When true, frames will not be read from the device. */ +bool g_bPause = false; +/* When true, only a single frame will be read, and then reading will be paused. */ +bool g_bStep = false; +bool g_bShutdown = false; + +glut_perspective_reshaper reshaper; +glut_callbacks cb; + +IntPair mouseLocation; +IntPair windowSize; + +// -------------------------------- +// Utilities +// -------------------------------- +void motionCallback(int x, int y) +{ + mouseInputMotion(int((double)x/windowSize.X*WIN_SIZE_X), int((double)y/windowSize.Y*WIN_SIZE_Y)); +} + +void mouseCallback(int button, int state, int x, int y) +{ + mouseInputButton(button, state, int((double)x/windowSize.X*WIN_SIZE_X), int((double)y/windowSize.Y*WIN_SIZE_Y)); +} + +void keyboardCallback(unsigned char key, int /*x*/, int /*y*/) +{ + if (isCapturing()) + { + captureStop(0); + } + else + { + handleKey(key); + } + + glutPostRedisplay(); +} + +void keyboardSpecialCallback(int key, int /*x*/, int /*y*/) +{ + if (isCapturing()) + { + captureStop(0); + } + else + { + handleSpecialKey(key); + } + + glutPostRedisplay(); +} + +void reshapeCallback(int width, int height) +{ + windowSize.X = width; + windowSize.Y = height; + windowReshaped(width, height); +} + +void idleCallback() +{ + if (g_bShutdown) + { + return; + } + + if (g_bPause != TRUE) + { + // read a frame + readFrame(); + + captureRun(); + } + + if (g_bStep == TRUE) + { + g_bStep = FALSE; + g_bPause = TRUE; + } + + glutPostRedisplay(); +} + +void step(int) +{ + g_bStep = true; + g_bPause = false; +} + +void seek(int nDiff) +{ + seekFrame(nDiff); + + // now step the last one (that way, if seek is not supported, as in sensor, at least one frame + // will be read). + if (g_bPause) + { + step(0); + } +} + +void seekToUserInputEnd(bool ok, const char* userInput) +{ + if (ok) + { + long seekFrame = atoi(userInput); + seekFrameAbs(seekFrame); + + // now step the last one (that way, if seek is not supported, as in sensor, at least one frame + // will be read). + if (g_bPause) + { + step(0); + } + } +} + +void seekToUserInputBegin(int) +{ + startKeyboardInputMode("Seek to frame: ", true, seekToUserInputEnd); +} + +void init_opengl() +{ + glClearStencil(128); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glEnable(GL_NORMALIZE); + + glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); + GLfloat ambient[] = {0.5, 0.5, 0.5, 1}; + glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); + glLightf (GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.1f); +} + +void closeSample(int errCode) +{ + captureStop(0); + g_bShutdown = TRUE; + closeDevice(); + + if (errCode != ERR_OK) + { + printf("Press any key to continue . . .\n"); + _getch(); + } + + exit(errCode); +} + +void togglePause(int) +{ + g_bPause = !g_bPause; + setPlaybackSpeed(g_bPause ? -1.0f : 1.0f); +} + +void startCapture(int delay) +{ + if (g_bPause) + { + displayMessage("Cannot record when paused!"); + } + else + { + captureStart(delay); + } +} + +void createKeyboardMap() +{ + startKeyboardMap(); + { + startKeyboardGroup(KEYBOARD_GROUP_PRESETS); + { + registerKey('1', getPresetName(1), setPreset, 1); + registerKey('2', getPresetName(2), setPreset, 2); + registerKey('3', getPresetName(3), setPreset, 3); + registerKey('4', getPresetName(4), setPreset, 4); + registerKey('5', getPresetName(5), setPreset, 5); + registerKey('7', getPresetName(7), setPreset, 7); + registerKey('8', getPresetName(8), setPreset, 8); + registerKey('9', getPresetName(9), setPreset, 9); + registerKey('0', getPresetName(10), setPreset, 10); + registerKey('-', getPresetName(11), setPreset, 11); + registerKey('=', getPresetName(12), setPreset, 12); + } + endKeyboardGroup(); + startKeyboardGroup(KEYBOARD_GROUP_DEVICE); + { + registerKey('y', "Frame sync on/off", toggleFrameSync, 0); + registerKey('m', "Mirror on/off", toggleMirror, 0); + registerKey('/', "Reset all croppings", resetAllCropping, 0); + + registerKey('a', "toggle Auto Exposure", toggleImageAutoExposure, 0); + registerKey('q', "toggle AWB", toggleImageAutoWhiteBalance, 0); + registerKey('e', "Increase Exposure", changeImageExposure, 1); + registerKey('E', "Decrease Exposure", changeImageExposure, -1); + registerKey('g', "Increase Gain", changeImageGain, 10); + registerKey('G', "Decrease Gain", changeImageGain, -10); + + registerKey('x', "Close Range", toggleCloseRange, 0); + registerKey('i', "Toggle Image Registration", toggleImageRegistration, 0); + } + endKeyboardGroup(); + startKeyboardGroup(KEYBOARD_GROUP_CAPTURE); + { + registerKey('s', "Start", startCapture, 0); + registerKey('d', "Start (5 sec delay)", startCapture, 5); + registerKey('x', "Stop", captureStop, 0); + registerKey('c', "Capture current frame only", captureSingleFrame, 0); + } + endKeyboardGroup(); + startKeyboardGroup(KEYBOARD_GROUP_DISPLAY); + { + registerKey('p', "Pointer Mode On/Off", togglePointerMode, 0); + registerKey('f', "Full Screen On/Off", toggleFullScreen, 0); + registerKey('?', "Show/Hide Help screen", toggleHelpScreen, 0); + } + endKeyboardGroup(); + startKeyboardGroup(KEYBOARD_GROUP_GENERAL); + { + registerKey('?', "Show/Hide help screen", toggleHelpScreen, 0); + registerKey(27, "Exit", closeSample, ERR_OK); + } + endKeyboardGroup(); + startKeyboardGroup(KEYBOARD_GROUP_PLAYER); + { + registerKey(' ', "Pause/Resume", togglePause, 0); + if (getDevice().isFile()) + { + registerSpecialKey(GLUT_KEY_RIGHT, "Jump 1 frame forward", seek, 1); + registerSpecialKey(GLUT_KEY_UP, "Jump 10 frames forward", seek, 10); + registerSpecialKey(GLUT_KEY_LEFT, "Jump 1 frame backwards", seek, -1); + registerSpecialKey(GLUT_KEY_DOWN, "Jump 10 frames backwards", seek, -10); + registerKey(':', "Jump to frame", seekToUserInputBegin, 0); + + registerKey('r', "Toggle playback repeat", togglePlaybackRepeat, 0); + registerKey('[', "Decrease playback speed", changePlaybackSpeed, -1); + registerKey(']', "Increase playback speed", changePlaybackSpeed, 1); + } + registerKey(';', "Read one frame", step, 0); + } + endKeyboardGroup(); + } + endKeyboardMap(); +} + +void createMenu() +{ + const openni::SensorInfo* pSensorInfo; + + startMenu(); + { + startSubMenu("View"); + { + startSubMenu("Presets"); + { + for (int i = 1; i < PRESET_COUNT; ++i) + { + if (i != 6) + createMenuEntry(getPresetName(i), setPreset, i); + } + } + endSubMenu(); + startSubMenu("Screen Layout"); + { + createMenuEntry("Side by Side", setScreenLayout, (int)SIDE_BY_SIDE); + createMenuEntry("Overlay", setScreenLayout, (int)OVERLAY); + } + endSubMenu(); + startSubMenu("Depth"); + { + for (int i = 0; i < NUM_OF_DEPTH_DRAW_TYPES; ++i) + { + createMenuEntry(g_DepthDrawColoring[i], setDepthDrawing, i); + } + } + endSubMenu(); + startSubMenu("Color"); + { + for (int i = 0; i < NUM_OF_COLOR_DRAW_TYPES; ++i) + { + createMenuEntry(g_ColorDrawColoring[i], setColorDrawing, i); + } + } + endSubMenu(); + createMenuEntry("Pointer Mode On/Off", togglePointerMode, 0); + createMenuEntry("Show/Hide Help Screen", toggleHelpScreen, 0); + } + endSubMenu(); + startSubMenu("Device"); + { + startSubMenu("Streams"); + { + // Depth stream + pSensorInfo = getDepthSensorInfo(); + if (pSensorInfo != NULL) + { + startSubMenu("Depth"); + { + createMenuEntry("On/Off", toggleDepthState, 0); + startSubMenu("Video Mode"); + { + const openni::Array& supportedModes = pSensorInfo->getSupportedVideoModes(); + for (int i = 0; i < supportedModes.getSize(); ++i) + { + const openni::VideoMode* pSupportedMode = &supportedModes[i]; + char name[50]; + XnUInt32 dummy; + xnOSStrFormat(name, 50, &dummy, "%d x %d @ %d (%s)", pSupportedMode->getResolutionX(), pSupportedMode->getResolutionY(), pSupportedMode->getFps(), getFormatName(pSupportedMode->getPixelFormat())); + createMenuEntry(name, setDepthVideoMode, i); + } + } + endSubMenu(); + createMenuEntry("Mirror", toggleDepthMirror, 0); + createMenuEntry("Reset Cropping", resetDepthCropping, 0); + } + endSubMenu(); + } + + // Color stream + pSensorInfo = getColorSensorInfo(); + if (pSensorInfo != NULL) + { + startSubMenu("Color"); + { + createMenuEntry("On/Off", toggleColorState, 0); + startSubMenu("Video Mode"); + { + const openni::Array& supportedModes = pSensorInfo->getSupportedVideoModes(); + for (int i = 0; i < supportedModes.getSize(); ++i) + { + const openni::VideoMode* pSupportedMode = &supportedModes[i]; + char name[50]; + XnUInt32 dummy; + xnOSStrFormat(name, 50, &dummy, "%d x %d @ %d (%s)", pSupportedMode->getResolutionX(), pSupportedMode->getResolutionY(), pSupportedMode->getFps(), getFormatName(pSupportedMode->getPixelFormat())); + createMenuEntry(name, setColorVideoMode, i); + } + } + endSubMenu(); + createMenuEntry("Mirror", toggleColorMirror, 0); + createMenuEntry("Reset Cropping", resetColorCropping, 0); + } + endSubMenu(); + } + + // IR stream + pSensorInfo = getIRSensorInfo(); + if (pSensorInfo != NULL) + { + startSubMenu("IR"); + { + createMenuEntry("On/Off", toggleIRState, 0); + startSubMenu("Video Mode"); + { + const openni::Array& supportedModes = pSensorInfo->getSupportedVideoModes(); + for (int i = 0; i < supportedModes.getSize(); ++i) + { + const openni::VideoMode* pSupportedMode = &supportedModes[i]; + char name[50]; + XnUInt32 dummy; + xnOSStrFormat(name, 50, &dummy, "%d x %d @ %d (%s)", pSupportedMode->getResolutionX(), pSupportedMode->getResolutionY(), pSupportedMode->getFps(), getFormatName(pSupportedMode->getPixelFormat())); + createMenuEntry(name, setIRVideoMode, i); + } + } + endSubMenu(); + createMenuEntry("Mirror", toggleIRMirror, 0); + createMenuEntry("Reset Cropping", resetIRCropping, 0); + } + endSubMenu(); + } +// startSubMenu("Audio"); +// { +// createMenuEntry("On/Off", toggleAudioState, 0); +// } +// endSubMenu(); +// startSubMenu("Primary Stream"); +// { +// for (int i = 0; i < g_PrimaryStream.nValuesCount; ++i) +// { +// createMenuEntry(g_PrimaryStream.pValues[i], changePrimaryStream, i); +// } +// } +// endSubMenu(); + } + endSubMenu(); + startSubMenu("Registration"); + { + for (int i = 0; i < g_Registration.nValuesCount; ++i) + { + unsigned int nValue = g_Registration.pValues[i]; + createMenuEntry(g_Registration.pValueToName[nValue], changeRegistration, nValue); + } + } + endSubMenu(); + createMenuEntry("Frame Sync", toggleFrameSync, 0); + createMenuEntry("Mirror All", toggleMirror, 0); + } + endSubMenu(); + startSubMenu("Capture"); + { + startSubMenu("Depth Capturing"); + { + for (int i = 0; i < g_DepthCapturing.nValuesCount; ++i) + { + unsigned int nValue = g_DepthCapturing.pValues[i]; + createMenuEntry(g_DepthCapturing.pValueToName[i], captureSetDepthFormat, nValue); + } + } + endSubMenu(); + startSubMenu("Image Capturing"); + { + for (int i = 0; i < g_ColorCapturing.nValuesCount; ++i) + { + unsigned int nValue = g_ColorCapturing.pValues[i]; + createMenuEntry(g_ColorCapturing.pValueToName[i], captureSetColorFormat, nValue); + } + } + endSubMenu(); + startSubMenu("IR Capturing"); + { + for (int i = 0; i < g_IRCapturing.nValuesCount; ++i) + { + unsigned int nValue = g_IRCapturing.pValues[i]; + createMenuEntry(g_IRCapturing.pValueToName[i], captureSetIRFormat, nValue); + } + } + endSubMenu(); + createMenuEntry("Browse", captureBrowse, 0); + createMenuEntry("Start", startCapture, 0); + createMenuEntry("Start (5 sec delay)", startCapture, 5); + createMenuEntry("Restart", captureRestart, 0); + createMenuEntry("Stop", captureStop, 0); + } + endSubMenu(); + startSubMenu("Player"); + { + createMenuEntry("Pause/Resume", togglePause, 0); + if (getDevice().isFile()) + { + createMenuEntry("Skip 1 frame forward", seek, 1); + createMenuEntry("Skip 10 frame forward", seek, 10); + createMenuEntry("Skip 1 frame backwards", seek, -1); + createMenuEntry("Skip 10 frame backwards", seek, -10); + + createMenuEntry("Toggle playback repeat", togglePlaybackRepeat, 0); + + createMenuEntry("Decrease playback speed", changePlaybackSpeed, -1); + createMenuEntry("Increase playback speed", changePlaybackSpeed, 1); + } + createMenuEntry("Read one frame", step, 0); + } + endSubMenu(); + createMenuEntry("Quit", closeSample, ERR_OK); + } + endMenu(); +} + +void onExit() +{ + closeDevice(); + captureStop(0); +} + +int changeDirectory(char* arg0) +{ + // get dir name + XnChar strDirName[XN_FILE_MAX_PATH]; + XnStatus nRetVal = xnOSGetDirName(arg0, strDirName, XN_FILE_MAX_PATH); + XN_IS_STATUS_OK(nRetVal); + + // now set current directory to it + nRetVal = xnOSSetCurrentDir(strDirName); + XN_IS_STATUS_OK(nRetVal); + + return 0; +} + +int main(int argc, char **argv) +{ + XnBool bChooseDevice = FALSE; + const char* uri = NULL; + + DeviceConfig config; + config.openDepth = SENSOR_TRY; + config.openColor = SENSOR_TRY; + config.openIR = SENSOR_TRY; + + for (int i = 1; i < argc; ++i) + { + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { + printf("Usage: %s [OPTIONS] [URI]\n\n", argv[0]); + printf("When URI is provided, OpenNI will attempt to open the device with said URI. When not provided, \n"); + printf("any device might be opened.\n\n"); + printf("Options:\n"); + printf("-h, --help\n"); + printf(" Shows this help screen and exits\n"); + printf("-devices\n"); + printf(" Allows to choose the device to open from the list of connected devices\n"); + printf("-depth=\n"); + printf("-color=\n"); + printf("-ir=\n"); + printf(" Toggles each stream state. means the stream will not be opened. means it will be opened, and NiViewer will\n"); + printf(" exit if it fails. means that NiViewer will try to open that stream, but continue if it fails.\n"); + printf(" The default value is for all 3 sensors.\n"); + return 0; + } + else if (strcmp(argv[i], "-devices") == 0) + { + bChooseDevice = TRUE; + } + else if (strcmp(argv[i], "-depth=on") == 0) + { + config.openDepth = SENSOR_ON; + } + else if (strcmp(argv[i], "-depth=off") == 0) + { + config.openDepth = SENSOR_OFF; + } + else if (strcmp(argv[i], "-depth=try") == 0) + { + config.openDepth = SENSOR_TRY; + } + else if (strcmp(argv[i], "-color=on") == 0) + { + config.openColor = SENSOR_ON; + } + else if (strcmp(argv[i], "-color=off") == 0) + { + config.openColor = SENSOR_OFF; + } + else if (strcmp(argv[i], "-color=try") == 0) + { + config.openColor = SENSOR_TRY; + } + else if (strcmp(argv[i], "-ir=on") == 0) + { + config.openIR = SENSOR_ON; + } + else if (strcmp(argv[i], "-ir=off") == 0) + { + config.openIR = SENSOR_OFF; + } + else if (strcmp(argv[i], "-ir=try") == 0) + { + config.openIR = SENSOR_TRY; + } + else if (uri == NULL) + { + uri = argv[i]; + } + else + { + printf("unknown argument: %s\n", argv[i]); + return -1; + } + } + + // Xiron Init + XnStatus rc = XN_STATUS_OK; + + if (bChooseDevice) + { + rc = openDeviceFromList(config); + } + else + { + rc = openDevice(uri, config); + } + + if (rc != openni::STATUS_OK) + { + printf("openDevice failed:\n%s\n", openni::OpenNI::getExtendedError()); + closeSample(ERR_DEVICE); + } + +// audioInit(); + captureInit(); + + reshaper.zNear = 1; + reshaper.zFar = 100; + glut_add_interactor(&reshaper); + + cb.mouse_function = mouseCallback; + cb.motion_function = motionCallback; + cb.passive_motion_function = motionCallback; + cb.keyboard_function = keyboardCallback; + cb.special_function = keyboardSpecialCallback; + cb.reshape_function = reshapeCallback; + glut_add_interactor(&cb); + + glutInit(&argc, argv); + glutInitDisplayString("stencil double rgb"); + glutInitWindowSize(WIN_SIZE_X, WIN_SIZE_Y); + glutCreateWindow("OpenNI Viewer"); + glutFullScreen(); + glutSetCursor(GLUT_CURSOR_NONE); + + init_opengl(); + + glut_helpers_initialize(); + + glutIdleFunc(idleCallback); + glutDisplayFunc(drawFrame); + + drawInit(); + createKeyboardMap(); + createMenu(); + + atexit(onExit); + + // Per frame code is in drawFrame() + glutMainLoop(); + +// audioShutdown(); + + closeSample(ERR_OK); + +// return (ERR_OK); +} diff --git a/Source/Tools/NiViewer/NiViewer.vcxproj b/Source/Tools/NiViewer/NiViewer.vcxproj new file mode 100644 index 0000000..0a4c392 --- /dev/null +++ b/Source/Tools/NiViewer/NiViewer.vcxproj @@ -0,0 +1,201 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {BDA3BF24-550A-4BF9-83E5-0056134EED40} + NiViewer + + + + Application + true + MultiByte + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)Bin\$(Platform)-$(Configuration)\ + $(SolutionDir)Bin\Intermediate\$(Platform)-$(Configuration)\$(ProjectName)\ + + + + Disabled + ..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;..\..\..\ThirdParty\GL + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + Comdlg32.lib;OpenNI2.lib;XnLib.lib;glut32.lib + $(OutDir);..\..\..\ThirdParty\GL + true + + + ..\..\..\Include + + + + + Disabled + ..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;..\..\..\ThirdParty\GL + _WINDLL;%(PreprocessorDefinitions) + Level4 + true + true + + + + + true + Comdlg32.lib;OpenNI2.lib;XnLib.lib;glut64.lib + $(OutDir);..\..\..\ThirdParty\GL + true + + + ..\..\..\Include + + + + + Level4 + MaxSpeed + true + ..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;..\..\..\ThirdParty\GL + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + Comdlg32.lib;OpenNI2.lib;XnLib.lib;glut32.lib + $(OutDir);..\..\..\ThirdParty\GL + true + + + ..\..\..\Include + + + + + Level4 + MaxSpeed + true + ..\..\..\ThirdParty\PSCommon\XnLib\Include;..\..\..\Include;..\..\..\ThirdParty\GL + true + AnySuitable + Speed + true + true + false + StreamingSIMDExtensions2 + Fast + true + + + true + true + true + Comdlg32.lib;OpenNI2.lib;XnLib.lib;glut64.lib + $(OutDir);..\..\..\ThirdParty\GL + true + + + ..\..\..\Include + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ThirdParty/Android.mk b/ThirdParty/Android.mk new file mode 100644 index 0000000..a609aef --- /dev/null +++ b/ThirdParty/Android.mk @@ -0,0 +1,17 @@ +# OpenNI 2.x Android makefile. +# Copyright (C) 2012 PrimeSense Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Recurse through all subdirs +include $(call all-subdir-makefiles) diff --git a/ThirdParty/GL/glh/glh_array.h b/ThirdParty/GL/glh/glh_array.h new file mode 100644 index 0000000..f1dc986 --- /dev/null +++ b/ThirdParty/GL/glh/glh_array.h @@ -0,0 +1,274 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Cass Everitt - cass@r3.nu +*/ + +// Simple array template. +// Copyright (c) Cass W. Everitt 1999 +// Copyright (c) NVIDIA 2000 + +#ifndef _GLH_ARRAY_H_ +#define _GLH_ARRAY_H_ + +namespace glh +{ + // + // Template for array2 + // + + template class array2 + { + public: + typedef T value_type; + + array2(int width=1, int height=1) + { + w = width; + h = height; + data = new T [w * h]; + clear(T()); + } + + array2(const array2 & t) + { + w = h = 0; + data=0; + (*this) = t; + } + + // intentionally non-virtual + ~array2() { delete [] data; } + + const array2 & operator = (const array2 & t) + { + if(w != t.w || h != t.h) set_size(t.w, t.h); + int sz = w * h; + for(int i = 0; i < sz; i++) data[i] = t.data[i]; + return *this; + } + + + void set_size(int width, int height) + + { + + if(w == width && h == height) return; + + delete [] data; + + w = width; + + h = height; + + data = new T [w * h]; + + } + + + T & operator () (int i, int j) + { return data[i + j * w]; } + + const T & operator () (int i, int j) const + { return data[i + j * w]; } + + + int get_width() const { return w; } + + int get_height() const { return h; } + + void clear(const T & val) + { + int sz = w * h; + for(int i = 0; i < sz; i++) data[i] = val; + } + + + void copy(const array2 & src, int i_offset = 0, int j_offset = 0, + int width = 0, int height = 0) + + { + + int io = i_offset; + + int jo = j_offset; + + if(width == 0) width = src.get_width(); + + if(height == 0) height = src.get_height(); + + if(io + width > w) return; + + if(jo + height > h) return; + + for(int i=0; i < width; i++) + + for(int j=0; j < height; j++) + + (*this)(io+i, jo+j) = src(i,j); + + } + + + + T * get_pointer() { return data; } + + const T * get_pointer() const { return data; } + private: + + int w, h; + T * data; + }; + + // + // Template for array3 + // + + template class array3 + { + public: + typedef T value_type; + + array3(int width=1, int height=1, int depth=1) + { + w = width; + h = height; + d = depth; + data = new T [w * h * d]; + clear(T()); + } + + array3(const array3 & t) + { + w = h = d = 0; + data=0; + (*this) = t; + } + + // intentionally non-virtual + ~array3() { delete [] data; } + + const array3 & operator = (const array3 & t) + { + if(w != t.w || h != t.h || d != t.d) + set_size(t.w, t.h, t.d); + int sz = w * h * d; + for(int i = 0; i < sz; i++) + data[i] = t.data[i]; + return *this; + } + + + void set_size(int width, int height, int depth) + + { + if(w == width && h == height && d == depth) + return; + + delete [] data; + + w = width; + h = height; + d = depth; + + data = new T [w * h * d]; + } + + + T & operator () (int i, int j, int k) + { return data[i + j * w + k * w * h]; } + + const T & operator () (int i, int j, int k) const + { return data[i + j * w + k * w * h]; } + + + int get_width() const + { return w; } + + int get_height() const + { return h; } + + int get_depth() const + { return d; } + + void clear(const T & val) + { + int sz = w * h * d; + for(int i = 0; i < sz; i++) + data[i] = val; + } + + + void copy(const array3 & src, + int i_offset = 0, int j_offset = 0, int k_offset = 0, + int width = 0, int height = 0, int depth = 0) + { + + int io = i_offset; + int jo = j_offset; + int ko = k_offset; + + if(width == 0) + width = src.get_width(); + if(height == 0) + height = src.get_height(); + if(depth == 0) + depth = src.get_depth(); + + if(io + width > w || jo + height > h || ko + depth > d) + return; + + for(int i=0; i < width; i++) + for(int j=0; j < height; j++) + for(int k=0; k < depth; k++) + (*this)(io+i, jo+j, ko+k) = src(i,j,k); + } + + + + T * get_pointer() + { return data; } + + const T * get_pointer() const + { return data; } + + private: + int w, h, d; + T * data; + }; // template class array3 +} // namespace glh +#endif diff --git a/ThirdParty/GL/glh/glh_convenience.h b/ThirdParty/GL/glh/glh_convenience.h new file mode 100644 index 0000000..9dae8c9 --- /dev/null +++ b/ThirdParty/GL/glh/glh_convenience.h @@ -0,0 +1,209 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_CONVENIENCE_H +#define GLH_CONVENIENCE_H + +// Convenience methods for using glh_linear objects +// with opengl... + + + +// debugging hack... +#include + +using namespace std; + +#ifdef MACOS +#include +#else +#include +#endif + +#include +#include + + +namespace glh +{ + + // matrix helpers + + inline matrix4f get_matrix(GLenum matrix) + { + GLfloat m[16]; + glGetFloatv(matrix, m); + return matrix4f(m); + } + + // transform helpers + + inline void glh_rotate(const quaternionf & r) + { + float angle; + vec3f axis; + r.get_value(axis, angle); + glRotatef(to_degrees(angle), axis.v[0], axis.v[1], axis.v[2]); + } + + // inverse of camera_lookat + inline matrix4f object_lookat(const vec3f & from, const vec3f & to, const vec3f & Up) + { + vec3f look = to - from; + look.normalize(); + vec3f up(Up); + up -= look * look.dot(up); + up.normalize(); + + quaternionf r(vec3f(0,0,-1), vec3f(0,1,0), look, up); + matrix4f m; + r.get_value(m); + m.set_translate(from); + return m; + } + + + // inverse of object_lookat + inline matrix4f camera_lookat(const vec3f & eye, const vec3f & lookpoint, const vec3f & Up) + { + vec3f look = lookpoint - eye; + look.normalize(); + vec3f up(Up); + up -= look * look.dot(up); + up.normalize(); + + matrix4f t; + t.set_translate(-eye); + + quaternionf r(vec3f(0,0,-1), vec3f(0,1,0), look, up); + r.invert(); + matrix4f rm; + r.get_value(rm); + return rm*t; + } + + + inline matrix4f frustum(float left, float right, + float bottom, float top, + float zNear, float zFar) + { + matrix4f m; + m.make_identity(); + + m(0,0) = (2*zNear) / (right - left); + m(0,2) = (right + left) / (right - left); + + m(1,1) = (2*zNear) / (top - bottom); + m(1,2) = (top + bottom) / (top - bottom); + + m(2,2) = -(zFar + zNear) / (zFar - zNear); + m(2,3) = -2*zFar*zNear / (zFar - zNear); + + m(3,2) = -1; + m(3,3) = 0; + + return m; + } + + inline matrix4f frustum_inverse(float left, float right, + float bottom, float top, + float zNear, float zFar) + { + matrix4f m; + m.make_identity(); + + m(0,0) = (right - left) / (2 * zNear); + m(0,3) = (right + left) / (2 * zNear); + + m(1,1) = (top - bottom) / (2 * zNear); + m(1,3) = (top + bottom) / (2 * zNear); + + m(2,2) = 0; + m(2,3) = -1; + + m(3,2) = -(zFar - zNear) / (2 * zFar * zNear); + m(3,3) = (zFar + zNear) / (2 * zFar * zNear); + + return m; + } + + inline matrix4f perspective(float fovy, float aspect, float zNear, float zFar) + { + double tangent = tan(to_radians(fovy/2.0f)); + float y = (float)tangent * zNear; + float x = aspect * y; + return frustum(-x, x, -y, y, zNear, zFar); + } + + inline matrix4f perspective_inverse(float fovy, float aspect, float zNear, float zFar) + { + double tangent = tan(to_radians(fovy/2.0f)); + float y = (float)tangent * zNear; + float x = aspect * y; + return frustum_inverse(-x, x, -y, y, zNear, zFar); + } + + + + // are these names ok? + + inline void set_texgen_planes(GLenum plane_type, const matrix4f & m) + { + GLenum coord[] = {GL_S, GL_T, GL_R, GL_Q }; + for(int i = 0; i < 4; i++) + { + vec4f row; + m.get_row(i,row); + glTexGenfv(coord[i], plane_type, row.v); + } + } + + // handy for register combiners + inline vec3f range_compress(const vec3f & v) + { vec3f vret(v); vret *= .5f; vret += .5f; return vret; } + + inline vec3f range_uncompress(const vec3f & v) + { vec3f vret(v); vret -= .5f; vret *= 2.f; return vret; } + +} // namespace glh + +#endif diff --git a/ThirdParty/GL/glh/glh_cube_map.h b/ThirdParty/GL/glh/glh_cube_map.h new file mode 100644 index 0000000..cacf334 --- /dev/null +++ b/ThirdParty/GL/glh/glh_cube_map.h @@ -0,0 +1,347 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +// some helper classes for making +// and using cube maps + +#ifndef GLH_CUBE_MAP_H +#define GLH_CUBE_MAP_H + +#ifdef MACOS +#include +#else +#include +#endif + +#include +#include +#include + +namespace glh +{ + +# ifdef GL_ARB_texture_cube_map +# define GLH_CUBE_MAP_POSITIVE_X GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB +# define GLH_CUBE_MAP_POSITIVE_Y GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB +# define GLH_CUBE_MAP_POSITIVE_Z GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB +# define GLH_CUBE_MAP_NEGATIVE_X GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB +# define GLH_CUBE_MAP_NEGATIVE_Y GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB +# define GLH_CUBE_MAP_NEGATIVE_Z GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB +# elif GL_EXT_texture_cube_map +# define GLH_CUBE_MAP_POSITIVE_X GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT +# define GLH_CUBE_MAP_POSITIVE_Y GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT +# define GLH_CUBE_MAP_POSITIVE_Z GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT +# define GLH_CUBE_MAP_NEGATIVE_X GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT +# define GLH_CUBE_MAP_NEGATIVE_Y GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT +# define GLH_CUBE_MAP_NEGATIVE_Z GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT +# endif + +# if GL_EXT_texture_cube_map || GL_ARB_texture_cube_map + // make a cube map from a functor + template + void make_cube_map(FunctionOfDirection & f, GLenum internal_format, + int size, int level = 0) + { + typedef typename FunctionOfDirection::Type Type; + int components = f.components; + GLenum type = f.type; + GLenum format = f.format; + Type * image = new Type[size*size*components]; + Type * ip; + + float offset = .5; + float delta = 1; + float halfsize = size/2.f; + vec3f v; + + // positive x image + { + ip = image; + for(int j = 0; j < size; j++) + { + for(int i=0; i < size; i++) + { + v[2] = -(i*delta + offset - halfsize); + v[1] = -(j*delta + offset - halfsize); + v[0] = halfsize; + v.normalize(); + f(v, ip); + ip += components; + } + } + glTexImage2D(GLH_CUBE_MAP_POSITIVE_X, + level, internal_format, size, size, 0, format, type, image); + } + // negative x image + { + ip = image; + for(int j = 0; j < size; j++) + { + for(int i=0; i < size; i++) + { + v[2] = (i*delta + offset - halfsize); + v[1] = -(j*delta + offset - halfsize); + v[0] = -halfsize; + v.normalize(); + f(v, ip); + ip += components; + } + } + glTexImage2D(GLH_CUBE_MAP_NEGATIVE_X, + level, internal_format, size, size, 0, format, type, image); + } + + // positive y image + { + ip = image; + for(int j = 0; j < size; j++) + { + for(int i=0; i < size; i++) + { + v[0] = (i*delta + offset - halfsize); + v[2] = (j*delta + offset - halfsize); + v[1] = halfsize; + v.normalize(); + f(v, ip); + ip += components; + } + } + glTexImage2D(GLH_CUBE_MAP_POSITIVE_Y, + level, internal_format, size, size, 0, format, type, image); + } + // negative y image + { + ip = image; + for(int j = 0; j < size; j++) + { + for(int i=0; i < size; i++) + { + v[0] = (i*delta + offset - halfsize); + v[2] = -(j*delta + offset - halfsize); + v[1] = -halfsize; + v.normalize(); + f(v, ip); + ip += components; + } + } + glTexImage2D(GLH_CUBE_MAP_NEGATIVE_Y, + level, internal_format, size, size, 0, format, type, image); + } + + // positive z image + { + ip = image; + for(int j = 0; j < size; j++) + { + for(int i=0; i < size; i++) + { + v[0] = (i*delta + offset - halfsize); + v[1] = -(j*delta + offset - halfsize); + v[2] = halfsize; + v.normalize(); + f(v, ip); + ip += components; + } + } + glTexImage2D(GLH_CUBE_MAP_POSITIVE_Z, + level, internal_format, size, size, 0, format, type, image); + } + // negative z image + { + ip = image; + for(int j = 0; j < size; j++) + { + for(int i=0; i < size; i++) + { + v[0] = -(i*delta + offset - halfsize); + v[1] = -(j*delta + offset - halfsize); + v[2] = -halfsize; + v.normalize(); + f(v, ip); + ip += components; + } + } + glTexImage2D(GLH_CUBE_MAP_NEGATIVE_Z, + level, internal_format, size, size, 0, format, type, image); + } + delete [] image; + } +# endif + + struct normalize_vector + { + typedef GLfloat Type; + int components; + GLenum type; + GLenum format; + normalize_vector() : components(3), type(GL_FLOAT), format(GL_RGB) {} + + void operator() (const vec3f & v, Type * t) + { + vec3f v2 = v; + v2 *= .5; + v2 += .5; + t[0] = v2[0]; + t[1] = v2[1]; + t[2] = v2[2]; + } + }; + + struct cube_map_unextended + { + cube_map_unextended() : + POSITIVE_X(0), NEGATIVE_X(1), + POSITIVE_Y(2), NEGATIVE_Y(3), + POSITIVE_Z(4), NEGATIVE_Z(5), + lightnum(GL_LIGHT0), angle(90.0) + {} + + // angle is > 90 degrees because we need a border to cull with + void set_angle(GLint width) + { angle = to_degrees((float)atan2(width/2.0f, width/2.0f-1.0f)) * 2.0f; } + + + void cull_for_projection(int i) + { + GLfloat plane[6][4] = + { + { 1, 0, 0, 0 }, + {-1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0,-1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0,-1, 0 } + }; + //glClipPlane(GL_CLIP_PLANE0, plane[i]); + glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 90); + glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, plane[i]); + } + + void cull_for_reflection(int i) + { + GLfloat dir[6][4] = + { + { 1, 0, 0, 0 }, + {-1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0,-1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0,-1, 0 } + }; + glLightfv(GL_LIGHT0, GL_POSITION, dir[i]); + } + + matrix4f get_matrix(int cubeface) + { + matrix4f m; + m = perspective((float)angle, 1.0f, 0.5f, 1.5f); + switch(cubeface) + { + case 0: + m *= camera_lookat(vec3f(0,0,0), vec3f( 1, 0, 0), vec3f(0,-1, 0)); + return m; + case 1: + m *= camera_lookat(vec3f(0,0,0), vec3f(-1, 0, 0), vec3f(0,-1, 0)); + return m; + case 2: + m *= camera_lookat(vec3f(0,0,0), vec3f( 0, 1, 0), vec3f(0, 0, 1)); + return m; + case 3: + m *= camera_lookat(vec3f(0,0,0), vec3f( 0,-1, 0), vec3f(0, 0,-1)); + return m; + case 4: + m *= camera_lookat(vec3f(0,0,0), vec3f( 0, 0, 1), vec3f(0,-1, 0)); + return m; + case 5: + m *= camera_lookat(vec3f(0,0,0), vec3f( 0, 0,-1), vec3f(0,-1, 0)); + return m; + default: + return matrix4f(); + } + } + + matrix4f get_matrix_inverse(int cubeface) + { + matrix4f m; + switch(cubeface) + { + case 0: + m = object_lookat(vec3f(0,0,0), vec3f( 1, 0, 0), vec3f(0,-1, 0)); + break; + case 1: + m = object_lookat(vec3f(0,0,0), vec3f(-1, 0, 0), vec3f(0,-1, 0)); + break; + case 2: + m = object_lookat(vec3f(0,0,0), vec3f( 0, 1, 0), vec3f(0, 0, 1)); + break; + case 3: + m = object_lookat(vec3f(0,0,0), vec3f( 0,-1, 0), vec3f(0, 0,-1)); + break; + case 4: + m = object_lookat(vec3f(0,0,0), vec3f( 0, 0, 1), vec3f(0,-1, 0)); + break; + case 5: + m = object_lookat(vec3f(0,0,0), vec3f( 0, 0,-1), vec3f(0,-1, 0)); + break; + default: + break; + } + return m * perspective_inverse((float)angle, 1.0f, 0.5f, 1.5f); + } + const int POSITIVE_X; + const int NEGATIVE_X; + const int POSITIVE_Y; + const int NEGATIVE_Y; + const int POSITIVE_Z; + const int NEGATIVE_Z; + + GLenum lightnum; + tex_object_2D face[6]; + double angle; + + matrix4f rotation; + + }; + +} // namespace glh +#endif + diff --git a/ThirdParty/GL/glh/glh_extensions.h b/ThirdParty/GL/glh/glh_extensions.h new file mode 100644 index 0000000..00a4abc --- /dev/null +++ b/ThirdParty/GL/glh/glh_extensions.h @@ -0,0 +1,297 @@ +// Comments: +// +// The trick with GLH_EXT_SINGLE_FILE is that you need to have it defined in +// exactly one cpp file because it piggy-backs the function implementations in +// the header. You don't want multiple implementations defined or the linker +// gets mad, but one file must have the implementations in it or the linker +// gets mad for different reasons. +// +// The goal was to avoid having this helper require a separate cpp file. One +// thing you could do is just have a glh_extensions.cpp that did +// +// #define GLH_EXT_SINGLE_FILE +// #include +// +// and make it the only file that ever defines GLH_EXT_SINGLE_FILE. + +#ifndef GLH_EXTENSIONS +#define GLH_EXTENSIONS + +#include +#include +#include + + +#if defined(WIN32) +# include +#endif + +#if _MSC_VER > 1500 +# include +#endif + +#ifdef MACOS +#include +#else +#include +#endif + +#ifdef WIN32 +# include +#endif + +#define CHECK_MEMORY(ptr) \ + if (NULL == ptr) { \ + printf("Error allocating memory in file %s, line %d\n", __FILE__, __LINE__); \ + exit(-1); \ + } + +#ifdef GLH_EXT_SINGLE_FILE +# define GLH_EXTENSIONS_SINGLE_FILE // have to do this because glh_genext.h unsets GLH_EXT_SINGLE_FILE +#endif + +#if (defined(WIN32) || defined(UNIX)) +#include "glh_genext.h" +#elif defined(MACOS) +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef GLH_EXTENSIONS_SINGLE_FILE +static char *unsupportedExts = NULL; +static char *sysExts = NULL; +#ifndef GL_SHADER_CONSISTENT_NV +#define GL_SHADER_CONSISTENT_NV 0x86DD +#endif +#ifndef GL_TEXTURE_SHADER_NV +#define GL_TEXTURE_SHADER_NV 0x86DE +#endif +#ifndef GL_SHADER_OPERATION_NV +#define GL_SHADER_OPERATION_NV 0x86DF +#endif + +static int ExtensionExists(const char* extName, const char* sysExts) +{ + char *padExtName = (char*)malloc(strlen(extName) + 2); + strcat(strcpy(padExtName, extName), " "); + + if (0 == strcmp(extName, "GL_VERSION_1_2")) { + const char *version = (const char*)glGetString(GL_VERSION); + if (strstr(version, "1.0") == version || strstr(version, "1.1") == version) { + return GL_FALSE; + } else { + return GL_TRUE; + } + } + if (0 == strcmp(extName, "GL_VERSION_1_3")) { + const char *version = (const char*)glGetString(GL_VERSION); + if (strstr(version, "1.0") == version || + strstr(version, "1.1") == version || + strstr(version, "1.2") == version) { + return GL_FALSE; + } else { + return GL_TRUE; + } + } + if (0 == strcmp(extName, "GL_VERSION_1_4")) { + const char *version = (const char*)glGetString(GL_VERSION); + if (strstr(version, "1.0") == version || + strstr(version, "1.1") == version || + strstr(version, "1.2") == version || + strstr(version, "1.3") == version) { + return GL_FALSE; + } else { + return GL_TRUE; + } + } + if (0 == strcmp(extName, "GL_VERSION_1_5")) { + const char *version = (const char*)glGetString(GL_VERSION); + if (strstr(version, "1.0") == version || + strstr(version, "1.1") == version || + strstr(version, "1.2") == version || + strstr(version, "1.3") == version || + strstr(version, "1.4") == version) { + return GL_FALSE; + } else { + return GL_TRUE; + } + } + if (strstr(sysExts, padExtName)) { + free(padExtName); + return GL_TRUE; + } else { + free(padExtName); + return GL_FALSE; + } +} + +static const char* EatWhiteSpace(const char *str) +{ + for (; *str && (' ' == *str || '\t' == *str || '\n' == *str); str++); + return str; +} + +static const char* EatNonWhiteSpace(const char *str) +{ + for (; *str && (' ' != *str && '\t' != *str && '\n' != *str); str++); + return str; +} + +#if _MSC_VER > 1500 +// Suppress VS warning on using strdup +#pragma warning(disable: 4996) +#endif +int glh_init_extensions(const char *origReqExts) +{ + // Length of requested extensions string + size_t reqExtsLen; + char *reqExts; + // Ptr for individual extensions within reqExts + char *reqExt; + int success = GL_TRUE; + // build space-padded extension string + if (NULL == sysExts) { + const char *extensions = (const char*)glGetString(GL_EXTENSIONS); + size_t sysExtsLen = strlen(extensions); + const char *winsys_extensions = ""; + size_t winsysExtsLen = 0; +#if defined(WIN32) + { + PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = 0; + wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB"); + if(wglGetExtensionsStringARB) + { + winsys_extensions = wglGetExtensionsStringARB(wglGetCurrentDC()); + winsysExtsLen = strlen(winsys_extensions); + } + } +#elif defined(UNIX) + { + + winsys_extensions = glXQueryExtensionsString (glXGetCurrentDisplay(),DefaultScreen(glXGetCurrentDisplay())) ; + winsysExtsLen = strlen (winsys_extensions); + } +#endif + // Add 2 bytes, one for padding space, one for terminating NULL + sysExts = (char*)malloc(sysExtsLen + winsysExtsLen + 3); + CHECK_MEMORY(sysExts); + strcpy(sysExts, extensions); + sysExts[sysExtsLen] = ' '; + sysExts[sysExtsLen + 1] = 0; + strcat(sysExts, winsys_extensions); + sysExts[sysExtsLen + 1 + winsysExtsLen] = ' '; + sysExts[sysExtsLen + 1 + winsysExtsLen + 1] = 0; + } + + if (NULL == origReqExts) + return GL_TRUE; + reqExts = strdup(origReqExts); + reqExtsLen = strlen(reqExts); + if (NULL == unsupportedExts) { + unsupportedExts = (char*)malloc(reqExtsLen + 2); + } else if (reqExtsLen > strlen(unsupportedExts)) { + unsupportedExts = (char*)realloc(unsupportedExts, reqExtsLen + 2); + } + CHECK_MEMORY(unsupportedExts); + *unsupportedExts = 0; + + // Parse requested extension list + for (reqExt = reqExts; + (reqExt = (char*)EatWhiteSpace(reqExt)) != NULL && *reqExt; + reqExt = (char*)EatNonWhiteSpace(reqExt)) + { + char *extEnd = (char*)EatNonWhiteSpace(reqExt); + char saveChar = *extEnd; + *extEnd = (char)0; + +#if (defined(WIN32) || defined(UNIX)) + if (!ExtensionExists(reqExt, sysExts) || !glh_init_extension(reqExt)) { +#elif defined(MACOS) + if (!ExtensionExists(reqExt, sysExts)) { // don't try to get function pointers if on MacOS +#endif + // add reqExt to end of unsupportedExts + strcat(unsupportedExts, reqExt); + strcat(unsupportedExts, " "); + success = GL_FALSE; + } + *extEnd = saveChar; + } + free(reqExts); + return success; +} +#if _MSC_VER > 1500 +#pragma warning(default: 4996) +#endif + +const char* glh_get_unsupported_extensions() +{ + return (const char*)unsupportedExts; +} + +void glh_shutdown_extensions() +{ + if (unsupportedExts) + { + free(unsupportedExts); + unsupportedExts = NULL; + } + if (sysExts) + { + free(sysExts); + sysExts = NULL; + } +} + +int glh_extension_supported(const char *extension) +{ + static const GLubyte *extensions = NULL; + const GLubyte *start; + GLubyte *where, *terminator; + + // Extension names should not have spaces. + where = (GLubyte *) strchr(extension, ' '); + if (where || *extension == '\0') + return 0; + + if (!extensions) + extensions = glGetString(GL_EXTENSIONS); + + // It takes a bit of care to be fool-proof about parsing the + // OpenGL extensions string. Don't be fooled by sub-strings, + // etc. + start = extensions; + for (;;) + { + where = (GLubyte *) strstr((const char *) start, extension); + if (!where) + break; + terminator = where + strlen(extension); + if (where == start || *(where - 1) == ' ') + { + if (*terminator == ' ' || *terminator == '\0') + { + return 1; + } + } + start = terminator; + } + return 0; +} + +#else +int glh_init_extensions(const char *origReqExts); +const char* glh_get_unsupported_extensions(); +void glh_shutdown_extensions(); +int glh_extension_supported(const char *extension); +#endif /* GLH_EXT_SINGLE_FILE */ + +#ifdef __cplusplus +} +#endif + +#endif /* GLH_EXTENSIONS */ diff --git a/ThirdParty/GL/glh/glh_genext.h b/ThirdParty/GL/glh/glh_genext.h new file mode 100644 index 0000000..275c61d --- /dev/null +++ b/ThirdParty/GL/glh/glh_genext.h @@ -0,0 +1,5379 @@ +/* File generated by extgen.cpp -- do not modify */ +#ifndef GLH_GENEXT_H +#define GLH_GENEXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#if defined(WIN32) +# include +# define GLH_EXT_GET_PROC_ADDRESS(p) wglGetProcAddress(p) +#elif defined(UNIX) +# include +# include +# include +# define GLH_EXT_GET_PROC_ADDRESS(p) glXGetProcAddressARB( (const GLubyte *) p) +#endif + +#ifdef GLH_EXT_SINGLE_FILE +# define GLH_EXTERN +# define GLH_INITIALIZER = 0 +#else +# define GLH_EXTERN extern +# define GLH_INITIALIZER +#endif + +#define GLH__PREPROCESSOR_GYMNASTICS2(a,b) a##b +#define GLH__PREPROCESSOR_GYMNASTICS(a,b) GLH__PREPROCESSOR_GYMNASTICS2(a,b) + +#ifndef _WIN32 +#define GLH_EXT_PREFIX _ +#endif +#ifndef GLH_EXT_PREFIX +# define GLH_EXT_NAME(a) a +#else +# define GLH_EXT_NAME(a) GLH__PREPROCESSOR_GYMNASTICS(GLH_EXT_PREFIX,a) +#endif + +#ifndef _WIN32 +# ifndef GLH_CORE_PREFIX +# define GLH_CORE_PREFIX _ +# endif +#endif + +#ifndef GLH_CORE_PREFIX +# define GLH_CORE_NAME(a) a +#else +# define GLH_CORE_NAME(a) GLH__PREPROCESSOR_GYMNASTICS(GLH_CORE_PREFIX,a) +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_2) || defined(GL_VERSION_1_3) || defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ + GLH_EXTERN PFNGLBLENDCOLORPROC GLH_CORE_NAME(glBlendColor) GLH_INITIALIZER; + GLH_EXTERN PFNGLBLENDEQUATIONPROC GLH_CORE_NAME(glBlendEquation) GLH_INITIALIZER; + GLH_EXTERN PFNGLDRAWRANGEELEMENTSPROC GLH_CORE_NAME(glDrawRangeElements) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXIMAGE3DPROC GLH_CORE_NAME(glTexImage3D) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXSUBIMAGE3DPROC GLH_CORE_NAME(glTexSubImage3D) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOPYTEXSUBIMAGE3DPROC GLH_CORE_NAME(glCopyTexSubImage3D) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1DPROC GLH_CORE_NAME(glMultiTexCoord1d) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1DVPROC GLH_CORE_NAME(glMultiTexCoord1dv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1FPROC GLH_CORE_NAME(glMultiTexCoord1f) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1FVPROC GLH_CORE_NAME(glMultiTexCoord1fv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1IPROC GLH_CORE_NAME(glMultiTexCoord1i) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1IVPROC GLH_CORE_NAME(glMultiTexCoord1iv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1SPROC GLH_CORE_NAME(glMultiTexCoord1s) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1SVPROC GLH_CORE_NAME(glMultiTexCoord1sv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2DPROC GLH_CORE_NAME(glMultiTexCoord2d) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2DVPROC GLH_CORE_NAME(glMultiTexCoord2dv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2FPROC GLH_CORE_NAME(glMultiTexCoord2f) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2FVPROC GLH_CORE_NAME(glMultiTexCoord2fv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2IPROC GLH_CORE_NAME(glMultiTexCoord2i) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2IVPROC GLH_CORE_NAME(glMultiTexCoord2iv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2SPROC GLH_CORE_NAME(glMultiTexCoord2s) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2SVPROC GLH_CORE_NAME(glMultiTexCoord2sv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3DPROC GLH_CORE_NAME(glMultiTexCoord3d) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3DVPROC GLH_CORE_NAME(glMultiTexCoord3dv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3FPROC GLH_CORE_NAME(glMultiTexCoord3f) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3FVPROC GLH_CORE_NAME(glMultiTexCoord3fv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3IPROC GLH_CORE_NAME(glMultiTexCoord3i) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3IVPROC GLH_CORE_NAME(glMultiTexCoord3iv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3SPROC GLH_CORE_NAME(glMultiTexCoord3s) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3SVPROC GLH_CORE_NAME(glMultiTexCoord3sv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4DPROC GLH_CORE_NAME(glMultiTexCoord4d) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4DVPROC GLH_CORE_NAME(glMultiTexCoord4dv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4FPROC GLH_CORE_NAME(glMultiTexCoord4f) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4FVPROC GLH_CORE_NAME(glMultiTexCoord4fv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4IPROC GLH_CORE_NAME(glMultiTexCoord4i) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4IVPROC GLH_CORE_NAME(glMultiTexCoord4iv) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4SPROC GLH_CORE_NAME(glMultiTexCoord4s) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4SVPROC GLH_CORE_NAME(glMultiTexCoord4sv) GLH_INITIALIZER; + GLH_EXTERN PFNGLACTIVETEXTUREPROC GLH_CORE_NAME(glActiveTexture) GLH_INITIALIZER; + GLH_EXTERN PFNGLCLIENTACTIVETEXTUREPROC GLH_CORE_NAME(glClientActiveTexture) GLH_INITIALIZER; +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_3) || defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ + GLH_EXTERN PFNGLCOMPRESSEDTEXIMAGE3DPROC GLH_CORE_NAME(glCompressedTexImage3D) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXIMAGE2DPROC GLH_CORE_NAME(glCompressedTexImage2D) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXIMAGE1DPROC GLH_CORE_NAME(glCompressedTexImage1D) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC GLH_CORE_NAME(glCompressedTexSubImage3D) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC GLH_CORE_NAME(glCompressedTexSubImage2D) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC GLH_CORE_NAME(glCompressedTexSubImage1D) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOMPRESSEDTEXIMAGEPROC GLH_CORE_NAME(glGetCompressedTexImage) GLH_INITIALIZER; + GLH_EXTERN PFNGLSAMPLECOVERAGEPROC GLH_CORE_NAME(glSampleCoverage) GLH_INITIALIZER; + GLH_EXTERN PFNGLLOADTRANSPOSEMATRIXFPROC GLH_CORE_NAME(glLoadTransposeMatrixf) GLH_INITIALIZER; + GLH_EXTERN PFNGLLOADTRANSPOSEMATRIXDPROC GLH_CORE_NAME(glLoadTransposeMatrixd) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTTRANSPOSEMATRIXFPROC GLH_CORE_NAME(glMultTransposeMatrixf) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTTRANSPOSEMATRIXDPROC GLH_CORE_NAME(glMultTransposeMatrixd) GLH_INITIALIZER; +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ + GLH_EXTERN PFNGLMULTIDRAWARRAYSPROC GLH_CORE_NAME(glMultiDrawArrays) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTIDRAWELEMENTSPROC GLH_CORE_NAME(glMultiDrawElements) GLH_INITIALIZER; + GLH_EXTERN PFNGLPOINTPARAMETERFPROC GLH_CORE_NAME(glPointParameterf) GLH_INITIALIZER; + GLH_EXTERN PFNGLPOINTPARAMETERFVPROC GLH_CORE_NAME(glPointParameterfv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3BPROC GLH_CORE_NAME(glSecondaryColor3b) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3BVPROC GLH_CORE_NAME(glSecondaryColor3bv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3DPROC GLH_CORE_NAME(glSecondaryColor3d) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3DVPROC GLH_CORE_NAME(glSecondaryColor3dv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3FPROC GLH_CORE_NAME(glSecondaryColor3f) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3FVPROC GLH_CORE_NAME(glSecondaryColor3fv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3IPROC GLH_CORE_NAME(glSecondaryColor3i) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3IVPROC GLH_CORE_NAME(glSecondaryColor3iv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3SPROC GLH_CORE_NAME(glSecondaryColor3s) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3SVPROC GLH_CORE_NAME(glSecondaryColor3sv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UBPROC GLH_CORE_NAME(glSecondaryColor3ub) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UBVPROC GLH_CORE_NAME(glSecondaryColor3ubv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UIPROC GLH_CORE_NAME(glSecondaryColor3ui) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UIVPROC GLH_CORE_NAME(glSecondaryColor3uiv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3USPROC GLH_CORE_NAME(glSecondaryColor3us) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3USVPROC GLH_CORE_NAME(glSecondaryColor3usv) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLORPOINTERPROC GLH_CORE_NAME(glSecondaryColorPointer) GLH_INITIALIZER; + GLH_EXTERN PFNGLBLENDFUNCSEPARATEPROC GLH_CORE_NAME(glBlendFuncSeparate) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2DPROC GLH_CORE_NAME(glWindowPos2d) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2FPROC GLH_CORE_NAME(glWindowPos2f) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2IPROC GLH_CORE_NAME(glWindowPos2i) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2SPROC GLH_CORE_NAME(glWindowPos2s) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2DVPROC GLH_CORE_NAME(glWindowPos2dv) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2FVPROC GLH_CORE_NAME(glWindowPos2fv) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2IVPROC GLH_CORE_NAME(glWindowPos2iv) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2SVPROC GLH_CORE_NAME(glWindowPos2sv) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3DPROC GLH_CORE_NAME(glWindowPos3d) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3FPROC GLH_CORE_NAME(glWindowPos3f) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3IPROC GLH_CORE_NAME(glWindowPos3i) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3SPROC GLH_CORE_NAME(glWindowPos3s) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3DVPROC GLH_CORE_NAME(glWindowPos3dv) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3FVPROC GLH_CORE_NAME(glWindowPos3fv) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3IVPROC GLH_CORE_NAME(glWindowPos3iv) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3SVPROC GLH_CORE_NAME(glWindowPos3sv) GLH_INITIALIZER; +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ + GLH_EXTERN PFNGLGENQUERIESPROC GLH_CORE_NAME(glGenQueries) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEQUERIESPROC GLH_CORE_NAME(glDeleteQueries) GLH_INITIALIZER; + GLH_EXTERN PFNGLISQUERYPROC GLH_CORE_NAME(glIsQuery) GLH_INITIALIZER; + GLH_EXTERN PFNGLBEGINQUERYPROC GLH_CORE_NAME(glBeginQuery) GLH_INITIALIZER; + GLH_EXTERN PFNGLENDQUERYPROC GLH_CORE_NAME(glEndQuery) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETQUERYIVPROC GLH_CORE_NAME(glGetQueryiv) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETQUERYOBJECTIVPROC GLH_CORE_NAME(glGetQueryObjectiv) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETQUERYOBJECTUIVPROC GLH_CORE_NAME(glGetQueryObjectuiv) GLH_INITIALIZER; + GLH_EXTERN PFNGLBINDBUFFERPROC GLH_CORE_NAME(glBindBuffer) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEBUFFERSPROC GLH_CORE_NAME(glDeleteBuffers) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENBUFFERSPROC GLH_CORE_NAME(glGenBuffers) GLH_INITIALIZER; + GLH_EXTERN PFNGLISBUFFERPROC GLH_CORE_NAME(glIsBuffer) GLH_INITIALIZER; + GLH_EXTERN PFNGLBUFFERDATAPROC GLH_CORE_NAME(glBufferData) GLH_INITIALIZER; + GLH_EXTERN PFNGLBUFFERSUBDATAPROC GLH_CORE_NAME(glBufferSubData) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETBUFFERSUBDATAPROC GLH_CORE_NAME(glGetBufferSubData) GLH_INITIALIZER; + GLH_EXTERN PFNGLMAPBUFFERPROC GLH_CORE_NAME(glMapBuffer) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNMAPBUFFERPROC GLH_CORE_NAME(glUnmapBuffer) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETBUFFERPARAMETERIVPROC GLH_CORE_NAME(glGetBufferParameteriv) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETBUFFERPOINTERVPROC GLH_CORE_NAME(glGetBufferPointerv) GLH_INITIALIZER; +#endif +#endif + +#ifdef GL_ARB_depth_texture +#endif + +#ifdef GL_ARB_fragment_program +#endif + +#ifdef GL_ARB_fragment_program_shadow +#endif + +#ifdef GL_ARB_fragment_shader +#endif + +#ifdef GL_ARB_matrix_palette +#endif + +#ifdef GL_ARB_multisample +#endif + +#ifdef GL_ARB_multitexture + GLH_EXTERN PFNGLMULTITEXCOORD1DARBPROC GLH_EXT_NAME(glMultiTexCoord1dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1DVARBPROC GLH_EXT_NAME(glMultiTexCoord1dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1FARBPROC GLH_EXT_NAME(glMultiTexCoord1fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1FVARBPROC GLH_EXT_NAME(glMultiTexCoord1fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1IARBPROC GLH_EXT_NAME(glMultiTexCoord1iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1IVARBPROC GLH_EXT_NAME(glMultiTexCoord1ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1SARBPROC GLH_EXT_NAME(glMultiTexCoord1sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1SVARBPROC GLH_EXT_NAME(glMultiTexCoord1svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2DARBPROC GLH_EXT_NAME(glMultiTexCoord2dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2DVARBPROC GLH_EXT_NAME(glMultiTexCoord2dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2FARBPROC GLH_EXT_NAME(glMultiTexCoord2fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2FVARBPROC GLH_EXT_NAME(glMultiTexCoord2fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2IARBPROC GLH_EXT_NAME(glMultiTexCoord2iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2IVARBPROC GLH_EXT_NAME(glMultiTexCoord2ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2SARBPROC GLH_EXT_NAME(glMultiTexCoord2sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2SVARBPROC GLH_EXT_NAME(glMultiTexCoord2svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3DARBPROC GLH_EXT_NAME(glMultiTexCoord3dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3DVARBPROC GLH_EXT_NAME(glMultiTexCoord3dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3FARBPROC GLH_EXT_NAME(glMultiTexCoord3fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3FVARBPROC GLH_EXT_NAME(glMultiTexCoord3fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3IARBPROC GLH_EXT_NAME(glMultiTexCoord3iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3IVARBPROC GLH_EXT_NAME(glMultiTexCoord3ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3SARBPROC GLH_EXT_NAME(glMultiTexCoord3sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3SVARBPROC GLH_EXT_NAME(glMultiTexCoord3svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4DARBPROC GLH_EXT_NAME(glMultiTexCoord4dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4DVARBPROC GLH_EXT_NAME(glMultiTexCoord4dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4FARBPROC GLH_EXT_NAME(glMultiTexCoord4fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4FVARBPROC GLH_EXT_NAME(glMultiTexCoord4fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4IARBPROC GLH_EXT_NAME(glMultiTexCoord4iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4IVARBPROC GLH_EXT_NAME(glMultiTexCoord4ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4SARBPROC GLH_EXT_NAME(glMultiTexCoord4sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4SVARBPROC GLH_EXT_NAME(glMultiTexCoord4svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLACTIVETEXTUREARBPROC GLH_EXT_NAME(glActiveTextureARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCLIENTACTIVETEXTUREARBPROC GLH_EXT_NAME(glClientActiveTextureARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_occlusion_query + GLH_EXTERN PFNGLGENQUERIESARBPROC GLH_EXT_NAME(glGenQueriesARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEQUERIESARBPROC GLH_EXT_NAME(glDeleteQueriesARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLISQUERYARBPROC GLH_EXT_NAME(glIsQueryARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLBEGINQUERYARBPROC GLH_EXT_NAME(glBeginQueryARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLENDQUERYARBPROC GLH_EXT_NAME(glEndQueryARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETQUERYIVARBPROC GLH_EXT_NAME(glGetQueryivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETQUERYOBJECTIVARBPROC GLH_EXT_NAME(glGetQueryObjectivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETQUERYOBJECTUIVARBPROC GLH_EXT_NAME(glGetQueryObjectuivARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_point_parameters + GLH_EXTERN PFNGLPOINTPARAMETERFARBPROC GLH_EXT_NAME(glPointParameterfARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPOINTPARAMETERFVARBPROC GLH_EXT_NAME(glPointParameterfvARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_point_sprite +#endif + +#ifdef GL_ARB_shader_objects + GLH_EXTERN PFNGLDELETEOBJECTARBPROC GLH_EXT_NAME(glDeleteObjectARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETHANDLEARBPROC GLH_EXT_NAME(glGetHandleARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLDETACHOBJECTARBPROC GLH_EXT_NAME(glDetachObjectARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCREATESHADEROBJECTARBPROC GLH_EXT_NAME(glCreateShaderObjectARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLSHADERSOURCEARBPROC GLH_EXT_NAME(glShaderSourceARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPILESHADERARBPROC GLH_EXT_NAME(glCompileShaderARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCREATEPROGRAMOBJECTARBPROC GLH_EXT_NAME(glCreateProgramObjectARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLATTACHOBJECTARBPROC GLH_EXT_NAME(glAttachObjectARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLLINKPROGRAMARBPROC GLH_EXT_NAME(glLinkProgramARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUSEPROGRAMOBJECTARBPROC GLH_EXT_NAME(glUseProgramObjectARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVALIDATEPROGRAMARBPROC GLH_EXT_NAME(glValidateProgramARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM1FARBPROC GLH_EXT_NAME(glUniform1fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM2FARBPROC GLH_EXT_NAME(glUniform2fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM3FARBPROC GLH_EXT_NAME(glUniform3fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM4FARBPROC GLH_EXT_NAME(glUniform4fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM1IARBPROC GLH_EXT_NAME(glUniform1iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM2IARBPROC GLH_EXT_NAME(glUniform2iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM3IARBPROC GLH_EXT_NAME(glUniform3iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM4IARBPROC GLH_EXT_NAME(glUniform4iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM1FVARBPROC GLH_EXT_NAME(glUniform1fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM2FVARBPROC GLH_EXT_NAME(glUniform2fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM3FVARBPROC GLH_EXT_NAME(glUniform3fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM4FVARBPROC GLH_EXT_NAME(glUniform4fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM1IVARBPROC GLH_EXT_NAME(glUniform1ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM2IVARBPROC GLH_EXT_NAME(glUniform2ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM3IVARBPROC GLH_EXT_NAME(glUniform3ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORM4IVARBPROC GLH_EXT_NAME(glUniform4ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORMMATRIX2FVARBPROC GLH_EXT_NAME(glUniformMatrix2fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORMMATRIX3FVARBPROC GLH_EXT_NAME(glUniformMatrix3fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNIFORMMATRIX4FVARBPROC GLH_EXT_NAME(glUniformMatrix4fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETOBJECTPARAMETERFVARBPROC GLH_EXT_NAME(glGetObjectParameterfvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETOBJECTPARAMETERIVARBPROC GLH_EXT_NAME(glGetObjectParameterivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETINFOLOGARBPROC GLH_EXT_NAME(glGetInfoLogARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETATTACHEDOBJECTSARBPROC GLH_EXT_NAME(glGetAttachedObjectsARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETUNIFORMLOCATIONARBPROC GLH_EXT_NAME(glGetUniformLocationARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETACTIVEUNIFORMARBPROC GLH_EXT_NAME(glGetActiveUniformARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETUNIFORMFVARBPROC GLH_EXT_NAME(glGetUniformfvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETUNIFORMIVARBPROC GLH_EXT_NAME(glGetUniformivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETSHADERSOURCEARBPROC GLH_EXT_NAME(glGetShaderSourceARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_shadow +#endif + +#ifdef GL_ARB_shadow_ambient +#endif + +#ifdef GL_ARB_texture_border_clamp +#endif + +#ifdef GL_ARB_texture_compression + GLH_EXTERN PFNGLCOMPRESSEDTEXIMAGE3DARBPROC GLH_EXT_NAME(glCompressedTexImage3DARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXIMAGE2DARBPROC GLH_EXT_NAME(glCompressedTexImage2DARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXIMAGE1DARBPROC GLH_EXT_NAME(glCompressedTexImage1DARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC GLH_EXT_NAME(glCompressedTexSubImage3DARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC GLH_EXT_NAME(glCompressedTexSubImage2DARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC GLH_EXT_NAME(glCompressedTexSubImage1DARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOMPRESSEDTEXIMAGEARBPROC GLH_EXT_NAME(glGetCompressedTexImageARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_texture_cube_map +#endif + +#ifdef GL_ARB_texture_env_add +#endif + +#ifdef GL_ARB_texture_env_combine +#endif + +#ifdef GL_ARB_texture_env_dot3 +#endif + +#ifdef GL_ARB_texture_mirrored_repeat +#endif + +#ifdef GL_ARB_texture_non_power_of_two +#endif + +#ifdef GL_ARB_texture_rectangle +#endif + +#ifdef GL_ARB_transpose_matrix + GLH_EXTERN PFNGLLOADTRANSPOSEMATRIXFARBPROC GLH_EXT_NAME(glLoadTransposeMatrixfARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLLOADTRANSPOSEMATRIXDARBPROC GLH_EXT_NAME(glLoadTransposeMatrixdARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTTRANSPOSEMATRIXFARBPROC GLH_EXT_NAME(glMultTransposeMatrixfARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTTRANSPOSEMATRIXDARBPROC GLH_EXT_NAME(glMultTransposeMatrixdARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_vertex_buffer_object + GLH_EXTERN PFNGLBINDBUFFERARBPROC GLH_EXT_NAME(glBindBufferARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEBUFFERSARBPROC GLH_EXT_NAME(glDeleteBuffersARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENBUFFERSARBPROC GLH_EXT_NAME(glGenBuffersARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLISBUFFERARBPROC GLH_EXT_NAME(glIsBufferARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLBUFFERDATAARBPROC GLH_EXT_NAME(glBufferDataARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLBUFFERSUBDATAARBPROC GLH_EXT_NAME(glBufferSubDataARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETBUFFERSUBDATAARBPROC GLH_EXT_NAME(glGetBufferSubDataARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLMAPBUFFERARBPROC GLH_EXT_NAME(glMapBufferARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNMAPBUFFERARBPROC GLH_EXT_NAME(glUnmapBufferARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETBUFFERPARAMETERIVARBPROC GLH_EXT_NAME(glGetBufferParameterivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETBUFFERPOINTERVARBPROC GLH_EXT_NAME(glGetBufferPointervARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_vertex_program + GLH_EXTERN PFNGLVERTEXATTRIB1SARBPROC GLH_EXT_NAME(glVertexAttrib1sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1FARBPROC GLH_EXT_NAME(glVertexAttrib1fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1DARBPROC GLH_EXT_NAME(glVertexAttrib1dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2SARBPROC GLH_EXT_NAME(glVertexAttrib2sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2FARBPROC GLH_EXT_NAME(glVertexAttrib2fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2DARBPROC GLH_EXT_NAME(glVertexAttrib2dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3SARBPROC GLH_EXT_NAME(glVertexAttrib3sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3FARBPROC GLH_EXT_NAME(glVertexAttrib3fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3DARBPROC GLH_EXT_NAME(glVertexAttrib3dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4SARBPROC GLH_EXT_NAME(glVertexAttrib4sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4FARBPROC GLH_EXT_NAME(glVertexAttrib4fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4DARBPROC GLH_EXT_NAME(glVertexAttrib4dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4NUBARBPROC GLH_EXT_NAME(glVertexAttrib4NubARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1SVARBPROC GLH_EXT_NAME(glVertexAttrib1svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1FVARBPROC GLH_EXT_NAME(glVertexAttrib1fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1DVARBPROC GLH_EXT_NAME(glVertexAttrib1dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2SVARBPROC GLH_EXT_NAME(glVertexAttrib2svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2FVARBPROC GLH_EXT_NAME(glVertexAttrib2fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2DVARBPROC GLH_EXT_NAME(glVertexAttrib2dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3SVARBPROC GLH_EXT_NAME(glVertexAttrib3svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3FVARBPROC GLH_EXT_NAME(glVertexAttrib3fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3DVARBPROC GLH_EXT_NAME(glVertexAttrib3dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4BVARBPROC GLH_EXT_NAME(glVertexAttrib4bvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4SVARBPROC GLH_EXT_NAME(glVertexAttrib4svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4IVARBPROC GLH_EXT_NAME(glVertexAttrib4ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4UBVARBPROC GLH_EXT_NAME(glVertexAttrib4ubvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4USVARBPROC GLH_EXT_NAME(glVertexAttrib4usvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4UIVARBPROC GLH_EXT_NAME(glVertexAttrib4uivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4FVARBPROC GLH_EXT_NAME(glVertexAttrib4fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4DVARBPROC GLH_EXT_NAME(glVertexAttrib4dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4NBVARBPROC GLH_EXT_NAME(glVertexAttrib4NbvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4NSVARBPROC GLH_EXT_NAME(glVertexAttrib4NsvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4NIVARBPROC GLH_EXT_NAME(glVertexAttrib4NivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4NUBVARBPROC GLH_EXT_NAME(glVertexAttrib4NubvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4NUSVARBPROC GLH_EXT_NAME(glVertexAttrib4NusvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4NUIVARBPROC GLH_EXT_NAME(glVertexAttrib4NuivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBPOINTERARBPROC GLH_EXT_NAME(glVertexAttribPointerARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLENABLEVERTEXATTRIBARRAYARBPROC GLH_EXT_NAME(glEnableVertexAttribArrayARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLDISABLEVERTEXATTRIBARRAYARBPROC GLH_EXT_NAME(glDisableVertexAttribArrayARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMSTRINGARBPROC GLH_EXT_NAME(glProgramStringARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLBINDPROGRAMARBPROC GLH_EXT_NAME(glBindProgramARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEPROGRAMSARBPROC GLH_EXT_NAME(glDeleteProgramsARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENPROGRAMSARBPROC GLH_EXT_NAME(glGenProgramsARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMENVPARAMETER4DARBPROC GLH_EXT_NAME(glProgramEnvParameter4dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMENVPARAMETER4DVARBPROC GLH_EXT_NAME(glProgramEnvParameter4dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMENVPARAMETER4FARBPROC GLH_EXT_NAME(glProgramEnvParameter4fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMENVPARAMETER4FVARBPROC GLH_EXT_NAME(glProgramEnvParameter4fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMLOCALPARAMETER4DARBPROC GLH_EXT_NAME(glProgramLocalParameter4dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMLOCALPARAMETER4DVARBPROC GLH_EXT_NAME(glProgramLocalParameter4dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMLOCALPARAMETER4FARBPROC GLH_EXT_NAME(glProgramLocalParameter4fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMLOCALPARAMETER4FVARBPROC GLH_EXT_NAME(glProgramLocalParameter4fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMENVPARAMETERDVARBPROC GLH_EXT_NAME(glGetProgramEnvParameterdvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMENVPARAMETERFVARBPROC GLH_EXT_NAME(glGetProgramEnvParameterfvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC GLH_EXT_NAME(glGetProgramLocalParameterdvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC GLH_EXT_NAME(glGetProgramLocalParameterfvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMIVARBPROC GLH_EXT_NAME(glGetProgramivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMSTRINGARBPROC GLH_EXT_NAME(glGetProgramStringARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBDVARBPROC GLH_EXT_NAME(glGetVertexAttribdvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBFVARBPROC GLH_EXT_NAME(glGetVertexAttribfvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBIVARBPROC GLH_EXT_NAME(glGetVertexAttribivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBPOINTERVARBPROC GLH_EXT_NAME(glGetVertexAttribPointervARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLISPROGRAMARBPROC GLH_EXT_NAME(glIsProgramARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_vertex_shader + GLH_EXTERN PFNGLBINDATTRIBLOCATIONARBPROC GLH_EXT_NAME(glBindAttribLocationARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETACTIVEATTRIBARBPROC GLH_EXT_NAME(glGetActiveAttribARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETATTRIBLOCATIONARBPROC GLH_EXT_NAME(glGetAttribLocationARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ARB_window_pos + GLH_EXTERN PFNGLWINDOWPOS2DARBPROC GLH_EXT_NAME(glWindowPos2dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2FARBPROC GLH_EXT_NAME(glWindowPos2fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2IARBPROC GLH_EXT_NAME(glWindowPos2iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2SARBPROC GLH_EXT_NAME(glWindowPos2sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2DVARBPROC GLH_EXT_NAME(glWindowPos2dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2FVARBPROC GLH_EXT_NAME(glWindowPos2fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2IVARBPROC GLH_EXT_NAME(glWindowPos2ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS2SVARBPROC GLH_EXT_NAME(glWindowPos2svARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3DARBPROC GLH_EXT_NAME(glWindowPos3dARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3FARBPROC GLH_EXT_NAME(glWindowPos3fARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3IARBPROC GLH_EXT_NAME(glWindowPos3iARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3SARBPROC GLH_EXT_NAME(glWindowPos3sARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3DVARBPROC GLH_EXT_NAME(glWindowPos3dvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3FVARBPROC GLH_EXT_NAME(glWindowPos3fvARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3IVARBPROC GLH_EXT_NAME(glWindowPos3ivARB) GLH_INITIALIZER; + GLH_EXTERN PFNGLWINDOWPOS3SVARBPROC GLH_EXT_NAME(glWindowPos3svARB) GLH_INITIALIZER; +#endif + +#ifdef GL_ATI_draw_buffers + GLH_EXTERN PFNGLDRAWBUFFERSATIPROC GLH_EXT_NAME(glDrawBuffersATI) GLH_INITIALIZER; +#endif + +#ifdef GL_ATI_texture_float +#endif + +#ifdef GL_EXT_abgr +#endif + +#ifdef GL_EXT_bgra +#endif + +#ifdef GL_EXT_blend_color + GLH_EXTERN PFNGLBLENDCOLOREXTPROC GLH_EXT_NAME(glBlendColorEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_blend_equation_separate + GLH_EXTERN PFNGLBLENDEQUATIONSEPARATEEXTPROC GLH_EXT_NAME(glBlendEquationSeparateEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_blend_func_separate + GLH_EXTERN PFNGLBLENDFUNCSEPARATEEXTPROC GLH_EXT_NAME(glBlendFuncSeparateEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_blend_minmax + GLH_EXTERN PFNGLBLENDEQUATIONEXTPROC GLH_EXT_NAME(glBlendEquationEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_blend_subtract +#endif + +#ifdef GL_EXT_Cg_shader +#endif + +#ifdef GL_EXT_compiled_vertex_array + GLH_EXTERN PFNGLLOCKARRAYSEXTPROC GLH_EXT_NAME(glLockArraysEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLUNLOCKARRAYSEXTPROC GLH_EXT_NAME(glUnlockArraysEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_depth_bounds_test + GLH_EXTERN PFNGLDEPTHBOUNDSEXTPROC GLH_EXT_NAME(glDepthBoundsEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_draw_range_elements +#endif + +#ifdef GL_EXT_fog_coord + GLH_EXTERN PFNGLFOGCOORDDEXTPROC GLH_EXT_NAME(glFogCoorddEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFOGCOORDDVEXTPROC GLH_EXT_NAME(glFogCoorddvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFOGCOORDFEXTPROC GLH_EXT_NAME(glFogCoordfEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFOGCOORDFVEXTPROC GLH_EXT_NAME(glFogCoordfvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFOGCOORDPOINTEREXTPROC GLH_EXT_NAME(glFogCoordPointerEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_framebuffer_object + GLH_EXTERN PFNGLISRENDERBUFFEREXTPROC GLH_EXT_NAME(glIsRenderbufferEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLBINDRENDERBUFFEREXTPROC GLH_EXT_NAME(glBindRenderbufferEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETERENDERBUFFERSEXTPROC GLH_EXT_NAME(glDeleteRenderbuffersEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENRENDERBUFFERSEXTPROC GLH_EXT_NAME(glGenRenderbuffersEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLRENDERBUFFERSTORAGEEXTPROC GLH_EXT_NAME(glRenderbufferStorageEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC GLH_EXT_NAME(glGetRenderbufferParameterivEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLISFRAMEBUFFEREXTPROC GLH_EXT_NAME(glIsFramebufferEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLBINDFRAMEBUFFEREXTPROC GLH_EXT_NAME(glBindFramebufferEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEFRAMEBUFFERSEXTPROC GLH_EXT_NAME(glDeleteFramebuffersEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENFRAMEBUFFERSEXTPROC GLH_EXT_NAME(glGenFramebuffersEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC GLH_EXT_NAME(glCheckFramebufferStatusEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFRAMEBUFFERTEXTURE1DEXTPROC GLH_EXT_NAME(glFramebufferTexture1DEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFRAMEBUFFERTEXTURE2DEXTPROC GLH_EXT_NAME(glFramebufferTexture2DEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFRAMEBUFFERTEXTURE3DEXTPROC GLH_EXT_NAME(glFramebufferTexture3DEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC GLH_EXT_NAME(glFramebufferRenderbufferEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC GLH_EXT_NAME(glGetFramebufferAttachmentParameterivEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENERATEMIPMAPEXTPROC GLH_EXT_NAME(glGenerateMipmapEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_multi_draw_arrays + GLH_EXTERN PFNGLMULTIDRAWARRAYSEXTPROC GLH_EXT_NAME(glMultiDrawArraysEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTIDRAWELEMENTSEXTPROC GLH_EXT_NAME(glMultiDrawElementsEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_light_max_exponent +#endif + +#ifdef GL_EXT_packed_pixels +#endif + +#ifdef GL_EXT_paletted_texture + GLH_EXTERN PFNGLCOLORSUBTABLEEXTPROC GLH_EXT_NAME(glColorSubTableEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOLORTABLEEXTPROC GLH_EXT_NAME(glColorTableEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOLORTABLEEXTPROC GLH_EXT_NAME(glGetColorTableEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOLORTABLEPARAMETERFVEXTPROC GLH_EXT_NAME(glGetColorTableParameterfvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOLORTABLEPARAMETERIVEXTPROC GLH_EXT_NAME(glGetColorTableParameterivEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_pixel_buffer_object +#endif + +#ifdef GL_EXT_point_parameters + GLH_EXTERN PFNGLPOINTPARAMETERFEXTPROC GLH_EXT_NAME(glPointParameterfEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLPOINTPARAMETERFVEXTPROC GLH_EXT_NAME(glPointParameterfvEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_rescale_normal +#endif + +#ifdef GL_EXT_secondary_color + GLH_EXTERN PFNGLSECONDARYCOLOR3BEXTPROC GLH_EXT_NAME(glSecondaryColor3bEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3BVEXTPROC GLH_EXT_NAME(glSecondaryColor3bvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3DEXTPROC GLH_EXT_NAME(glSecondaryColor3dEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3DVEXTPROC GLH_EXT_NAME(glSecondaryColor3dvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3FEXTPROC GLH_EXT_NAME(glSecondaryColor3fEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3FVEXTPROC GLH_EXT_NAME(glSecondaryColor3fvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3IEXTPROC GLH_EXT_NAME(glSecondaryColor3iEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3IVEXTPROC GLH_EXT_NAME(glSecondaryColor3ivEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3SEXTPROC GLH_EXT_NAME(glSecondaryColor3sEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3SVEXTPROC GLH_EXT_NAME(glSecondaryColor3svEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UBEXTPROC GLH_EXT_NAME(glSecondaryColor3ubEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UBVEXTPROC GLH_EXT_NAME(glSecondaryColor3ubvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UIEXTPROC GLH_EXT_NAME(glSecondaryColor3uiEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3UIVEXTPROC GLH_EXT_NAME(glSecondaryColor3uivEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3USEXTPROC GLH_EXT_NAME(glSecondaryColor3usEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3USVEXTPROC GLH_EXT_NAME(glSecondaryColor3usvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLORPOINTEREXTPROC GLH_EXT_NAME(glSecondaryColorPointerEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_separate_specular_color +#endif + +#ifdef GL_EXT_shadow_funcs +#endif + +#ifdef GL_EXT_shared_texture_palette +#endif + +#ifdef GL_EXT_stencil_two_side + GLH_EXTERN PFNGLACTIVESTENCILFACEEXTPROC GLH_EXT_NAME(glActiveStencilFaceEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_stencil_wrap +#endif + +#ifdef GL_EXT_texture_compression_s3tc +#endif + +#ifdef GL_EXT_texture_cube_map +#endif + +#ifdef GL_EXT_texture_edge_clamp +#endif + +#ifdef GL_EXT_texture_env_add +#endif + +#ifdef GL_EXT_texture_env_combine +#endif + +#ifdef GL_EXT_texture_env_dot3 +#endif + +#ifdef GL_EXT_texture_filter_anisotropic +#endif + +#ifdef GL_EXT_texture_lod_bias +#endif + +#ifdef GL_EXT_texture_object + GLH_EXTERN PFNGLARETEXTURESRESIDENTEXTPROC GLH_EXT_NAME(glAreTexturesResidentEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLBINDTEXTUREEXTPROC GLH_EXT_NAME(glBindTextureEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETETEXTURESEXTPROC GLH_EXT_NAME(glDeleteTexturesEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENTEXTURESEXTPROC GLH_EXT_NAME(glGenTexturesEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLISTEXTUREEXTPROC GLH_EXT_NAME(glIsTextureEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLPRIORITIZETEXTURESEXTPROC GLH_EXT_NAME(glPrioritizeTexturesEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_texture_rectangle +#endif + +#ifdef GL_EXT_texture3D + GLH_EXTERN PFNGLTEXIMAGE3DEXTPROC GLH_EXT_NAME(glTexImage3DEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_vertex_array + GLH_EXTERN PFNGLARRAYELEMENTEXTPROC GLH_EXT_NAME(glArrayElementEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOLORPOINTEREXTPROC GLH_EXT_NAME(glColorPointerEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLEDGEFLAGPOINTEREXTPROC GLH_EXT_NAME(glEdgeFlagPointerEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPOINTERVEXTPROC GLH_EXT_NAME(glGetPointervEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLINDEXPOINTEREXTPROC GLH_EXT_NAME(glIndexPointerEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLNORMALPOINTEREXTPROC GLH_EXT_NAME(glNormalPointerEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORDPOINTEREXTPROC GLH_EXT_NAME(glTexCoordPointerEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXPOINTEREXTPROC GLH_EXT_NAME(glVertexPointerEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLDRAWARRAYSEXTPROC GLH_EXT_NAME(glDrawArraysEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_EXT_vertex_weighting + GLH_EXTERN PFNGLVERTEXWEIGHTFEXTPROC GLH_EXT_NAME(glVertexWeightfEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXWEIGHTFVEXTPROC GLH_EXT_NAME(glVertexWeightfvEXT) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXWEIGHTPOINTEREXTPROC GLH_EXT_NAME(glVertexWeightPointerEXT) GLH_INITIALIZER; +#endif + +#ifdef GL_HP_occlusion_test +#endif + +#ifdef GL_IBM_texture_mirrored_repeat +#endif + +#ifdef GL_NV_blend_square +#endif + +#ifdef GL_NV_copy_depth_to_color +#endif + +#ifdef GL_NV_depth_clamp +#endif + +#ifdef GL_NV_element_array + GLH_EXTERN PFNGLELEMENTPOINTERNVPROC GLH_EXT_NAME(glElementPointerNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLDRAWELEMENTARRAYNVPROC GLH_EXT_NAME(glDrawElementArrayNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLDRAWRANGEELEMENTARRAYNVPROC GLH_EXT_NAME(glDrawRangeElementArrayNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTIDRAWELEMENTARRAYNVPROC GLH_EXT_NAME(glMultiDrawElementArrayNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTIDRAWRANGEELEMENTARRAYNVPROC GLH_EXT_NAME(glMultiDrawRangeElementArrayNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_fence + GLH_EXTERN PFNGLGENFENCESNVPROC GLH_EXT_NAME(glGenFencesNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEFENCESNVPROC GLH_EXT_NAME(glDeleteFencesNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLSETFENCENVPROC GLH_EXT_NAME(glSetFenceNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTESTFENCENVPROC GLH_EXT_NAME(glTestFenceNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLFINISHFENCENVPROC GLH_EXT_NAME(glFinishFenceNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLISFENCENVPROC GLH_EXT_NAME(glIsFenceNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETFENCEIVNVPROC GLH_EXT_NAME(glGetFenceivNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_float_buffer +#endif + +#ifdef GL_NV_fog_distance +#endif + +#ifdef GL_NV_fragment_program + GLH_EXTERN PFNGLPROGRAMNAMEDPARAMETER4FNVPROC GLH_EXT_NAME(glProgramNamedParameter4fNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMNAMEDPARAMETER4DNVPROC GLH_EXT_NAME(glProgramNamedParameter4dNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC GLH_EXT_NAME(glProgramNamedParameter4fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC GLH_EXT_NAME(glProgramNamedParameter4dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC GLH_EXT_NAME(glGetProgramNamedParameterfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC GLH_EXT_NAME(glGetProgramNamedParameterdvNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_fragment_program2 +#endif + +#ifdef GL_NV_half_float + GLH_EXTERN PFNGLVERTEX2HNVPROC GLH_EXT_NAME(glVertex2hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEX2HVNVPROC GLH_EXT_NAME(glVertex2hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEX3HNVPROC GLH_EXT_NAME(glVertex3hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEX3HVNVPROC GLH_EXT_NAME(glVertex3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEX4HNVPROC GLH_EXT_NAME(glVertex4hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEX4HVNVPROC GLH_EXT_NAME(glVertex4hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLNORMAL3HNVPROC GLH_EXT_NAME(glNormal3hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLNORMAL3HVNVPROC GLH_EXT_NAME(glNormal3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOLOR3HNVPROC GLH_EXT_NAME(glColor3hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOLOR3HVNVPROC GLH_EXT_NAME(glColor3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOLOR4HNVPROC GLH_EXT_NAME(glColor4hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOLOR4HVNVPROC GLH_EXT_NAME(glColor4hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD1HNVPROC GLH_EXT_NAME(glTexCoord1hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD1HVNVPROC GLH_EXT_NAME(glTexCoord1hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD2HNVPROC GLH_EXT_NAME(glTexCoord2hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD2HVNVPROC GLH_EXT_NAME(glTexCoord2hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD3HNVPROC GLH_EXT_NAME(glTexCoord3hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD3HVNVPROC GLH_EXT_NAME(glTexCoord3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD4HNVPROC GLH_EXT_NAME(glTexCoord4hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTEXCOORD4HVNVPROC GLH_EXT_NAME(glTexCoord4hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1HNVPROC GLH_EXT_NAME(glMultiTexCoord1hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD1HVNVPROC GLH_EXT_NAME(glMultiTexCoord1hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2HNVPROC GLH_EXT_NAME(glMultiTexCoord2hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD2HVNVPROC GLH_EXT_NAME(glMultiTexCoord2hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3HNVPROC GLH_EXT_NAME(glMultiTexCoord3hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD3HVNVPROC GLH_EXT_NAME(glMultiTexCoord3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4HNVPROC GLH_EXT_NAME(glMultiTexCoord4hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLMULTITEXCOORD4HVNVPROC GLH_EXT_NAME(glMultiTexCoord4hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLFOGCOORDHNVPROC GLH_EXT_NAME(glFogCoordhNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLFOGCOORDHVNVPROC GLH_EXT_NAME(glFogCoordhvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3HNVPROC GLH_EXT_NAME(glSecondaryColor3hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLSECONDARYCOLOR3HVNVPROC GLH_EXT_NAME(glSecondaryColor3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1HNVPROC GLH_EXT_NAME(glVertexAttrib1hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1HVNVPROC GLH_EXT_NAME(glVertexAttrib1hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2HNVPROC GLH_EXT_NAME(glVertexAttrib2hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2HVNVPROC GLH_EXT_NAME(glVertexAttrib2hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3HNVPROC GLH_EXT_NAME(glVertexAttrib3hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3HVNVPROC GLH_EXT_NAME(glVertexAttrib3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4HNVPROC GLH_EXT_NAME(glVertexAttrib4hNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4HVNVPROC GLH_EXT_NAME(glVertexAttrib4hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS1HVNVPROC GLH_EXT_NAME(glVertexAttribs1hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS2HVNVPROC GLH_EXT_NAME(glVertexAttribs2hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS3HVNVPROC GLH_EXT_NAME(glVertexAttribs3hvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS4HVNVPROC GLH_EXT_NAME(glVertexAttribs4hvNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_light_max_exponent +#endif + +#ifdef GL_NV_multisample_filter_hint +#endif + +#ifdef GL_NV_occlusion_query + GLH_EXTERN PFNGLGENOCCLUSIONQUERIESNVPROC GLH_EXT_NAME(glGenOcclusionQueriesNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEOCCLUSIONQUERIESNVPROC GLH_EXT_NAME(glDeleteOcclusionQueriesNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLISOCCLUSIONQUERYNVPROC GLH_EXT_NAME(glIsOcclusionQueryNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLBEGINOCCLUSIONQUERYNVPROC GLH_EXT_NAME(glBeginOcclusionQueryNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLENDOCCLUSIONQUERYNVPROC GLH_EXT_NAME(glEndOcclusionQueryNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETOCCLUSIONQUERYIVNVPROC GLH_EXT_NAME(glGetOcclusionQueryivNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETOCCLUSIONQUERYUIVNVPROC GLH_EXT_NAME(glGetOcclusionQueryuivNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_packed_depth_stencil +#endif + +#ifdef GL_NV_pixel_buffer_object +#endif + +#ifdef GL_NV_pixel_data_range + GLH_EXTERN PFNGLPIXELDATARANGENVPROC GLH_EXT_NAME(glPixelDataRangeNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLFLUSHPIXELDATARANGENVPROC GLH_EXT_NAME(glFlushPixelDataRangeNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_point_sprite + GLH_EXTERN PFNGLPOINTPARAMETERINVPROC GLH_EXT_NAME(glPointParameteriNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPOINTPARAMETERIVNVPROC GLH_EXT_NAME(glPointParameterivNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_primitive_restart + GLH_EXTERN PFNGLPRIMITIVERESTARTNVPROC GLH_EXT_NAME(glPrimitiveRestartNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPRIMITIVERESTARTINDEXNVPROC GLH_EXT_NAME(glPrimitiveRestartIndexNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_register_combiners + GLH_EXTERN PFNGLCOMBINERPARAMETERFVNVPROC GLH_EXT_NAME(glCombinerParameterfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMBINERPARAMETERFNVPROC GLH_EXT_NAME(glCombinerParameterfNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMBINERPARAMETERIVNVPROC GLH_EXT_NAME(glCombinerParameterivNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMBINERPARAMETERINVPROC GLH_EXT_NAME(glCombinerParameteriNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMBINERINPUTNVPROC GLH_EXT_NAME(glCombinerInputNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLCOMBINEROUTPUTNVPROC GLH_EXT_NAME(glCombinerOutputNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLFINALCOMBINERINPUTNVPROC GLH_EXT_NAME(glFinalCombinerInputNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC GLH_EXT_NAME(glGetCombinerInputParameterfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC GLH_EXT_NAME(glGetCombinerInputParameterivNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC GLH_EXT_NAME(glGetCombinerOutputParameterfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC GLH_EXT_NAME(glGetCombinerOutputParameterivNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC GLH_EXT_NAME(glGetFinalCombinerInputParameterfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC GLH_EXT_NAME(glGetFinalCombinerInputParameterivNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_register_combiners2 + GLH_EXTERN PFNGLCOMBINERSTAGEPARAMETERFVNVPROC GLH_EXT_NAME(glCombinerStageParameterfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC GLH_EXT_NAME(glGetCombinerStageParameterfvNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_stencil_two_side + GLH_EXTERN PFNGLACTIVESTENCILFACENVPROC GLH_EXT_NAME(glActiveStencilFaceNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_texgen_reflection +#endif + +#ifdef GL_NV_texture_compression_vtc +#endif + +#ifdef GL_NV_texture_env_combine4 +#endif + +#ifdef GL_NV_texture_expand_normal +#endif + +#ifdef GL_NV_texture_rectangle +#endif + +#ifdef GL_NV_texture_shader +#endif + +#ifdef GL_NV_texture_shader2 +#endif + +#ifdef GL_NV_texture_shader3 +#endif + +#ifdef GL_NV_vertex_array_range + GLH_EXTERN PFNGLFLUSHVERTEXARRAYRANGENVPROC GLH_EXT_NAME(glFlushVertexArrayRangeNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXARRAYRANGENVPROC GLH_EXT_NAME(glVertexArrayRangeNV) GLH_INITIALIZER; +# ifdef _WIN32 + GLH_EXTERN PFNWGLALLOCATEMEMORYNVPROC GLH_EXT_NAME(wglAllocateMemoryNV) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXALLOCATEMEMORYNVPROC GLH_EXT_NAME(glXAllocateMemoryNV) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLFREEMEMORYNVPROC GLH_EXT_NAME(wglFreeMemoryNV) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXFREEMEMORYNVPROC GLH_EXT_NAME(glXFreeMemoryNV) GLH_INITIALIZER; +# endif +#endif + +#ifdef GL_NV_vertex_array_range2 +#endif + +#ifdef GL_NV_vertex_program + GLH_EXTERN PFNGLAREPROGRAMSRESIDENTNVPROC GLH_EXT_NAME(glAreProgramsResidentNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLBINDPROGRAMNVPROC GLH_EXT_NAME(glBindProgramNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLDELETEPROGRAMSNVPROC GLH_EXT_NAME(glDeleteProgramsNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLEXECUTEPROGRAMNVPROC GLH_EXT_NAME(glExecuteProgramNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGENPROGRAMSNVPROC GLH_EXT_NAME(glGenProgramsNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMPARAMETERDVNVPROC GLH_EXT_NAME(glGetProgramParameterdvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMPARAMETERFVNVPROC GLH_EXT_NAME(glGetProgramParameterfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMIVNVPROC GLH_EXT_NAME(glGetProgramivNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETPROGRAMSTRINGNVPROC GLH_EXT_NAME(glGetProgramStringNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETTRACKMATRIXIVNVPROC GLH_EXT_NAME(glGetTrackMatrixivNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBDVNVPROC GLH_EXT_NAME(glGetVertexAttribdvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBFVNVPROC GLH_EXT_NAME(glGetVertexAttribfvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBIVNVPROC GLH_EXT_NAME(glGetVertexAttribivNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLGETVERTEXATTRIBPOINTERVNVPROC GLH_EXT_NAME(glGetVertexAttribPointervNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLISPROGRAMNVPROC GLH_EXT_NAME(glIsProgramNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLLOADPROGRAMNVPROC GLH_EXT_NAME(glLoadProgramNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMPARAMETER4DNVPROC GLH_EXT_NAME(glProgramParameter4dNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMPARAMETER4DVNVPROC GLH_EXT_NAME(glProgramParameter4dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMPARAMETER4FNVPROC GLH_EXT_NAME(glProgramParameter4fNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMPARAMETER4FVNVPROC GLH_EXT_NAME(glProgramParameter4fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMPARAMETERS4DVNVPROC GLH_EXT_NAME(glProgramParameters4dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLPROGRAMPARAMETERS4FVNVPROC GLH_EXT_NAME(glProgramParameters4fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLREQUESTRESIDENTPROGRAMSNVPROC GLH_EXT_NAME(glRequestResidentProgramsNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLTRACKMATRIXNVPROC GLH_EXT_NAME(glTrackMatrixNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBPOINTERNVPROC GLH_EXT_NAME(glVertexAttribPointerNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1DNVPROC GLH_EXT_NAME(glVertexAttrib1dNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1DVNVPROC GLH_EXT_NAME(glVertexAttrib1dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1FNVPROC GLH_EXT_NAME(glVertexAttrib1fNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1FVNVPROC GLH_EXT_NAME(glVertexAttrib1fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1SNVPROC GLH_EXT_NAME(glVertexAttrib1sNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB1SVNVPROC GLH_EXT_NAME(glVertexAttrib1svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2DNVPROC GLH_EXT_NAME(glVertexAttrib2dNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2DVNVPROC GLH_EXT_NAME(glVertexAttrib2dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2FNVPROC GLH_EXT_NAME(glVertexAttrib2fNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2FVNVPROC GLH_EXT_NAME(glVertexAttrib2fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2SNVPROC GLH_EXT_NAME(glVertexAttrib2sNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB2SVNVPROC GLH_EXT_NAME(glVertexAttrib2svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3DNVPROC GLH_EXT_NAME(glVertexAttrib3dNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3DVNVPROC GLH_EXT_NAME(glVertexAttrib3dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3FNVPROC GLH_EXT_NAME(glVertexAttrib3fNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3FVNVPROC GLH_EXT_NAME(glVertexAttrib3fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3SNVPROC GLH_EXT_NAME(glVertexAttrib3sNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB3SVNVPROC GLH_EXT_NAME(glVertexAttrib3svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4DNVPROC GLH_EXT_NAME(glVertexAttrib4dNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4DVNVPROC GLH_EXT_NAME(glVertexAttrib4dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4FNVPROC GLH_EXT_NAME(glVertexAttrib4fNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4FVNVPROC GLH_EXT_NAME(glVertexAttrib4fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4SNVPROC GLH_EXT_NAME(glVertexAttrib4sNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4SVNVPROC GLH_EXT_NAME(glVertexAttrib4svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIB4UBVNVPROC GLH_EXT_NAME(glVertexAttrib4ubvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS1DVNVPROC GLH_EXT_NAME(glVertexAttribs1dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS1FVNVPROC GLH_EXT_NAME(glVertexAttribs1fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS1SVNVPROC GLH_EXT_NAME(glVertexAttribs1svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS2DVNVPROC GLH_EXT_NAME(glVertexAttribs2dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS2FVNVPROC GLH_EXT_NAME(glVertexAttribs2fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS2SVNVPROC GLH_EXT_NAME(glVertexAttribs2svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS3DVNVPROC GLH_EXT_NAME(glVertexAttribs3dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS3FVNVPROC GLH_EXT_NAME(glVertexAttribs3fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS3SVNVPROC GLH_EXT_NAME(glVertexAttribs3svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS4DVNVPROC GLH_EXT_NAME(glVertexAttribs4dvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS4FVNVPROC GLH_EXT_NAME(glVertexAttribs4fvNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS4SVNVPROC GLH_EXT_NAME(glVertexAttribs4svNV) GLH_INITIALIZER; + GLH_EXTERN PFNGLVERTEXATTRIBS4UBVNVPROC GLH_EXT_NAME(glVertexAttribs4ubvNV) GLH_INITIALIZER; +#endif + +#ifdef GL_NV_vertex_program1_1 +#endif + +#ifdef GL_NV_vertex_program2 +#endif + +#ifdef GL_NV_vertex_program2_option +#endif + +#ifdef GL_NV_vertex_program3 +#endif + +#ifdef GL_SGIS_generate_mipmap +#endif + +#ifdef GL_SGIS_texture_lod +#endif + +#ifdef GL_SGIX_depth_texture +#endif + +#ifdef GL_SGIX_shadow +#endif + +#ifdef GL_WIN_swap_hint + GLH_EXTERN PFNGLADDSWAPHINTRECTWINPROC GLH_EXT_NAME(glAddSwapHintRectWIN) GLH_INITIALIZER; +#endif + +#ifdef WGL_ARB_buffer_region +# ifdef _WIN32 + GLH_EXTERN PFNWGLCREATEBUFFERREGIONARBPROC GLH_EXT_NAME(wglCreateBufferRegionARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLDELETEBUFFERREGIONARBPROC GLH_EXT_NAME(wglDeleteBufferRegionARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLSAVEBUFFERREGIONARBPROC GLH_EXT_NAME(wglSaveBufferRegionARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLRESTOREBUFFERREGIONARBPROC GLH_EXT_NAME(wglRestoreBufferRegionARB) GLH_INITIALIZER; +# endif +#endif + +#ifdef WGL_ARB_extensions_string +# ifdef _WIN32 + GLH_EXTERN PFNWGLGETEXTENSIONSSTRINGARBPROC GLH_EXT_NAME(wglGetExtensionsStringARB) GLH_INITIALIZER; +# endif +#endif + +#ifdef WGL_ARB_pbuffer +# ifdef _WIN32 + GLH_EXTERN PFNWGLCREATEPBUFFERARBPROC GLH_EXT_NAME(wglCreatePbufferARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLGETPBUFFERDCARBPROC GLH_EXT_NAME(wglGetPbufferDCARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLRELEASEPBUFFERDCARBPROC GLH_EXT_NAME(wglReleasePbufferDCARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLDESTROYPBUFFERARBPROC GLH_EXT_NAME(wglDestroyPbufferARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLQUERYPBUFFERARBPROC GLH_EXT_NAME(wglQueryPbufferARB) GLH_INITIALIZER; +# endif +#endif + +#ifdef WGL_ARB_pixel_format +# ifdef _WIN32 + GLH_EXTERN PFNWGLGETPIXELFORMATATTRIBIVARBPROC GLH_EXT_NAME(wglGetPixelFormatAttribivARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLGETPIXELFORMATATTRIBFVARBPROC GLH_EXT_NAME(wglGetPixelFormatAttribfvARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLCHOOSEPIXELFORMATARBPROC GLH_EXT_NAME(wglChoosePixelFormatARB) GLH_INITIALIZER; +# endif +#endif + +#ifdef WGL_ARB_render_texture +# ifdef _WIN32 + GLH_EXTERN PFNWGLBINDTEXIMAGEARBPROC GLH_EXT_NAME(wglBindTexImageARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLRELEASETEXIMAGEARBPROC GLH_EXT_NAME(wglReleaseTexImageARB) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLSETPBUFFERATTRIBARBPROC GLH_EXT_NAME(wglSetPbufferAttribARB) GLH_INITIALIZER; +# endif +#endif + +#ifdef WGL_ATI_pixel_format_float +#endif + +#ifdef WGL_EXT_extensions_string +# ifdef _WIN32 + GLH_EXTERN PFNWGLGETEXTENSIONSSTRINGEXTPROC GLH_EXT_NAME(wglGetExtensionsStringEXT) GLH_INITIALIZER; +# endif +#endif + +#ifdef WGL_EXT_swap_control +# ifdef _WIN32 + GLH_EXTERN PFNWGLSWAPINTERVALEXTPROC GLH_EXT_NAME(wglSwapIntervalEXT) GLH_INITIALIZER; +# endif +# ifdef _WIN32 + GLH_EXTERN PFNWGLGETSWAPINTERVALEXTPROC GLH_EXT_NAME(wglGetSwapIntervalEXT) GLH_INITIALIZER; +# endif +#endif + +#ifdef WGL_NV_float_buffer +#endif + +#ifdef WGL_NV_render_depth_texture +#endif + +#ifdef WGL_NV_render_texture_rectangle +#endif + +#ifdef GLX_NV_float_buffer +#endif + +#ifdef GL_NVX_conditional_render + GLH_EXTERN PFNGLBEGINCONDITIONALRENDERNVXPROC GLH_EXT_NAME(glBeginConditionalRenderNVX) GLH_INITIALIZER; + GLH_EXTERN PFNGLENDCONDITIONALRENDERNVXPROC GLH_EXT_NAME(glEndConditionalRenderNVX) GLH_INITIALIZER; +#endif + +#ifdef GLX_SGIX_pbuffer +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXCREATEGLXPBUFFERSGIXPROC GLH_EXT_NAME(glXCreateGLXPbufferSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXDESTROYGLXPBUFFERSGIXPROC GLH_EXT_NAME(glXDestroyGLXPbufferSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXQUERYGLXPBUFFERSGIXPROC GLH_EXT_NAME(glXQueryGLXPbufferSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXSELECTEVENTSGIXPROC GLH_EXT_NAME(glXSelectEventSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXGETSELECTEDEVENTSGIXPROC GLH_EXT_NAME(glXGetSelectedEventSGIX) GLH_INITIALIZER; +# endif +#endif + +#ifdef GLX_SGIX_fbconfig +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXGETFBCONFIGATTRIBSGIXPROC GLH_EXT_NAME(glXGetFBConfigAttribSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXCHOOSEFBCONFIGSGIXPROC GLH_EXT_NAME(glXChooseFBConfigSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC GLH_EXT_NAME(glXCreateGLXPixmapWithConfigSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC GLH_EXT_NAME(glXCreateContextWithConfigSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXGETVISUALFROMFBCONFIGSGIXPROC GLH_EXT_NAME(glXGetVisualFromFBConfigSGIX) GLH_INITIALIZER; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXTERN PFNGLXGETFBCONFIGFROMVISUALSGIXPROC GLH_EXT_NAME(glXGetFBConfigFromVisualSGIX) GLH_INITIALIZER; +# endif +#endif + + +#ifdef GLH_EXT_SINGLE_FILE + +int glh_init_extension(const char* extension) +{ + if (NULL == extension) + return GL_FALSE; + +#ifndef _WIN32 + if (0 == strcmp(extension, "GL_VERSION_1_2") || 0 == strcmp(extension, "GL_VERSION_1_3") || 0 == strcmp(extension, "GL_VERSION_1_4") || 0 == strcmp(extension, "GL_VERSION_1_5")) + return GL_TRUE; +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_2) || defined(GL_VERSION_1_3) || defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + if (0 == strcmp(extension, "GL_VERSION_1_2")) { + GLH_CORE_NAME(glBlendColor) = (PFNGLBLENDCOLORPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendColor"); + if (NULL == GLH_CORE_NAME(glBlendColor)) + return GL_FALSE; + GLH_CORE_NAME(glBlendEquation) = (PFNGLBLENDEQUATIONPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendEquation"); + if (NULL == GLH_CORE_NAME(glBlendEquation)) + return GL_FALSE; + GLH_CORE_NAME(glDrawRangeElements) = (PFNGLDRAWRANGEELEMENTSPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawRangeElements"); + if (NULL == GLH_CORE_NAME(glDrawRangeElements)) + return GL_FALSE; + GLH_CORE_NAME(glTexImage3D) = (PFNGLTEXIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexImage3D"); + if (NULL == GLH_CORE_NAME(glTexImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glTexSubImage3D) = (PFNGLTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCopyTexSubImage3D) = (PFNGLCOPYTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCopyTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glCopyTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1d) = (PFNGLMULTITEXCOORD1DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1dv) = (PFNGLMULTITEXCOORD1DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1f) = (PFNGLMULTITEXCOORD1FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1fv) = (PFNGLMULTITEXCOORD1FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1i) = (PFNGLMULTITEXCOORD1IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1iv) = (PFNGLMULTITEXCOORD1IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1s) = (PFNGLMULTITEXCOORD1SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1sv) = (PFNGLMULTITEXCOORD1SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2d) = (PFNGLMULTITEXCOORD2DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2dv) = (PFNGLMULTITEXCOORD2DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2f) = (PFNGLMULTITEXCOORD2FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2fv) = (PFNGLMULTITEXCOORD2FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2i) = (PFNGLMULTITEXCOORD2IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2iv) = (PFNGLMULTITEXCOORD2IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2s) = (PFNGLMULTITEXCOORD2SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2sv) = (PFNGLMULTITEXCOORD2SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3d) = (PFNGLMULTITEXCOORD3DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3dv) = (PFNGLMULTITEXCOORD3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3f) = (PFNGLMULTITEXCOORD3FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3fv) = (PFNGLMULTITEXCOORD3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3i) = (PFNGLMULTITEXCOORD3IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3iv) = (PFNGLMULTITEXCOORD3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3s) = (PFNGLMULTITEXCOORD3SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3sv) = (PFNGLMULTITEXCOORD3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4d) = (PFNGLMULTITEXCOORD4DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4dv) = (PFNGLMULTITEXCOORD4DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4f) = (PFNGLMULTITEXCOORD4FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4fv) = (PFNGLMULTITEXCOORD4FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4i) = (PFNGLMULTITEXCOORD4IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4iv) = (PFNGLMULTITEXCOORD4IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4s) = (PFNGLMULTITEXCOORD4SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4sv) = (PFNGLMULTITEXCOORD4SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4sv)) + return GL_FALSE; + GLH_CORE_NAME(glActiveTexture) = (PFNGLACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glActiveTexture"); + if (NULL == GLH_CORE_NAME(glActiveTexture)) + return GL_FALSE; + GLH_CORE_NAME(glClientActiveTexture) = (PFNGLCLIENTACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glClientActiveTexture"); + if (NULL == GLH_CORE_NAME(glClientActiveTexture)) + return GL_FALSE; + + return GL_TRUE; + } +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_3) || defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + if (0 == strcmp(extension, "GL_VERSION_1_3")) { + GLH_CORE_NAME(glBlendColor) = (PFNGLBLENDCOLORPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendColor"); + if (NULL == GLH_CORE_NAME(glBlendColor)) + return GL_FALSE; + GLH_CORE_NAME(glBlendEquation) = (PFNGLBLENDEQUATIONPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendEquation"); + if (NULL == GLH_CORE_NAME(glBlendEquation)) + return GL_FALSE; + GLH_CORE_NAME(glDrawRangeElements) = (PFNGLDRAWRANGEELEMENTSPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawRangeElements"); + if (NULL == GLH_CORE_NAME(glDrawRangeElements)) + return GL_FALSE; + GLH_CORE_NAME(glTexImage3D) = (PFNGLTEXIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexImage3D"); + if (NULL == GLH_CORE_NAME(glTexImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glTexSubImage3D) = (PFNGLTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCopyTexSubImage3D) = (PFNGLCOPYTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCopyTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glCopyTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1d) = (PFNGLMULTITEXCOORD1DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1dv) = (PFNGLMULTITEXCOORD1DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1f) = (PFNGLMULTITEXCOORD1FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1fv) = (PFNGLMULTITEXCOORD1FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1i) = (PFNGLMULTITEXCOORD1IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1iv) = (PFNGLMULTITEXCOORD1IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1s) = (PFNGLMULTITEXCOORD1SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1sv) = (PFNGLMULTITEXCOORD1SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2d) = (PFNGLMULTITEXCOORD2DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2dv) = (PFNGLMULTITEXCOORD2DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2f) = (PFNGLMULTITEXCOORD2FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2fv) = (PFNGLMULTITEXCOORD2FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2i) = (PFNGLMULTITEXCOORD2IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2iv) = (PFNGLMULTITEXCOORD2IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2s) = (PFNGLMULTITEXCOORD2SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2sv) = (PFNGLMULTITEXCOORD2SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3d) = (PFNGLMULTITEXCOORD3DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3dv) = (PFNGLMULTITEXCOORD3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3f) = (PFNGLMULTITEXCOORD3FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3fv) = (PFNGLMULTITEXCOORD3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3i) = (PFNGLMULTITEXCOORD3IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3iv) = (PFNGLMULTITEXCOORD3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3s) = (PFNGLMULTITEXCOORD3SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3sv) = (PFNGLMULTITEXCOORD3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4d) = (PFNGLMULTITEXCOORD4DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4dv) = (PFNGLMULTITEXCOORD4DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4f) = (PFNGLMULTITEXCOORD4FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4fv) = (PFNGLMULTITEXCOORD4FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4i) = (PFNGLMULTITEXCOORD4IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4iv) = (PFNGLMULTITEXCOORD4IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4s) = (PFNGLMULTITEXCOORD4SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4sv) = (PFNGLMULTITEXCOORD4SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4sv)) + return GL_FALSE; + GLH_CORE_NAME(glActiveTexture) = (PFNGLACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glActiveTexture"); + if (NULL == GLH_CORE_NAME(glActiveTexture)) + return GL_FALSE; + GLH_CORE_NAME(glClientActiveTexture) = (PFNGLCLIENTACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glClientActiveTexture"); + if (NULL == GLH_CORE_NAME(glClientActiveTexture)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage3D) = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage3D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage2D) = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage2D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage2D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage1D) = (PFNGLCOMPRESSEDTEXIMAGE1DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage1D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage1D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage3D) = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage2D) = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage2D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage2D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage1D) = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage1D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage1D)) + return GL_FALSE; + GLH_CORE_NAME(glGetCompressedTexImage) = (PFNGLGETCOMPRESSEDTEXIMAGEPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCompressedTexImage"); + if (NULL == GLH_CORE_NAME(glGetCompressedTexImage)) + return GL_FALSE; + GLH_CORE_NAME(glSampleCoverage) = (PFNGLSAMPLECOVERAGEPROC)GLH_EXT_GET_PROC_ADDRESS("glSampleCoverage"); + if (NULL == GLH_CORE_NAME(glSampleCoverage)) + return GL_FALSE; + GLH_CORE_NAME(glLoadTransposeMatrixf) = (PFNGLLOADTRANSPOSEMATRIXFPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixf"); + if (NULL == GLH_CORE_NAME(glLoadTransposeMatrixf)) + return GL_FALSE; + GLH_CORE_NAME(glLoadTransposeMatrixd) = (PFNGLLOADTRANSPOSEMATRIXDPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixd"); + if (NULL == GLH_CORE_NAME(glLoadTransposeMatrixd)) + return GL_FALSE; + GLH_CORE_NAME(glMultTransposeMatrixf) = (PFNGLMULTTRANSPOSEMATRIXFPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixf"); + if (NULL == GLH_CORE_NAME(glMultTransposeMatrixf)) + return GL_FALSE; + GLH_CORE_NAME(glMultTransposeMatrixd) = (PFNGLMULTTRANSPOSEMATRIXDPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixd"); + if (NULL == GLH_CORE_NAME(glMultTransposeMatrixd)) + return GL_FALSE; + + return GL_TRUE; + } +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + if (0 == strcmp(extension, "GL_VERSION_1_4")) { + GLH_CORE_NAME(glBlendColor) = (PFNGLBLENDCOLORPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendColor"); + if (NULL == GLH_CORE_NAME(glBlendColor)) + return GL_FALSE; + GLH_CORE_NAME(glBlendEquation) = (PFNGLBLENDEQUATIONPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendEquation"); + if (NULL == GLH_CORE_NAME(glBlendEquation)) + return GL_FALSE; + GLH_CORE_NAME(glDrawRangeElements) = (PFNGLDRAWRANGEELEMENTSPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawRangeElements"); + if (NULL == GLH_CORE_NAME(glDrawRangeElements)) + return GL_FALSE; + GLH_CORE_NAME(glTexImage3D) = (PFNGLTEXIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexImage3D"); + if (NULL == GLH_CORE_NAME(glTexImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glTexSubImage3D) = (PFNGLTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCopyTexSubImage3D) = (PFNGLCOPYTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCopyTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glCopyTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1d) = (PFNGLMULTITEXCOORD1DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1dv) = (PFNGLMULTITEXCOORD1DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1f) = (PFNGLMULTITEXCOORD1FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1fv) = (PFNGLMULTITEXCOORD1FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1i) = (PFNGLMULTITEXCOORD1IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1iv) = (PFNGLMULTITEXCOORD1IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1s) = (PFNGLMULTITEXCOORD1SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1sv) = (PFNGLMULTITEXCOORD1SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2d) = (PFNGLMULTITEXCOORD2DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2dv) = (PFNGLMULTITEXCOORD2DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2f) = (PFNGLMULTITEXCOORD2FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2fv) = (PFNGLMULTITEXCOORD2FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2i) = (PFNGLMULTITEXCOORD2IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2iv) = (PFNGLMULTITEXCOORD2IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2s) = (PFNGLMULTITEXCOORD2SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2sv) = (PFNGLMULTITEXCOORD2SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3d) = (PFNGLMULTITEXCOORD3DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3dv) = (PFNGLMULTITEXCOORD3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3f) = (PFNGLMULTITEXCOORD3FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3fv) = (PFNGLMULTITEXCOORD3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3i) = (PFNGLMULTITEXCOORD3IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3iv) = (PFNGLMULTITEXCOORD3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3s) = (PFNGLMULTITEXCOORD3SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3sv) = (PFNGLMULTITEXCOORD3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4d) = (PFNGLMULTITEXCOORD4DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4dv) = (PFNGLMULTITEXCOORD4DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4f) = (PFNGLMULTITEXCOORD4FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4fv) = (PFNGLMULTITEXCOORD4FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4i) = (PFNGLMULTITEXCOORD4IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4iv) = (PFNGLMULTITEXCOORD4IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4s) = (PFNGLMULTITEXCOORD4SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4sv) = (PFNGLMULTITEXCOORD4SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4sv)) + return GL_FALSE; + GLH_CORE_NAME(glActiveTexture) = (PFNGLACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glActiveTexture"); + if (NULL == GLH_CORE_NAME(glActiveTexture)) + return GL_FALSE; + GLH_CORE_NAME(glClientActiveTexture) = (PFNGLCLIENTACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glClientActiveTexture"); + if (NULL == GLH_CORE_NAME(glClientActiveTexture)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage3D) = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage3D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage2D) = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage2D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage2D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage1D) = (PFNGLCOMPRESSEDTEXIMAGE1DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage1D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage1D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage3D) = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage2D) = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage2D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage2D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage1D) = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage1D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage1D)) + return GL_FALSE; + GLH_CORE_NAME(glGetCompressedTexImage) = (PFNGLGETCOMPRESSEDTEXIMAGEPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCompressedTexImage"); + if (NULL == GLH_CORE_NAME(glGetCompressedTexImage)) + return GL_FALSE; + GLH_CORE_NAME(glSampleCoverage) = (PFNGLSAMPLECOVERAGEPROC)GLH_EXT_GET_PROC_ADDRESS("glSampleCoverage"); + if (NULL == GLH_CORE_NAME(glSampleCoverage)) + return GL_FALSE; + GLH_CORE_NAME(glLoadTransposeMatrixf) = (PFNGLLOADTRANSPOSEMATRIXFPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixf"); + if (NULL == GLH_CORE_NAME(glLoadTransposeMatrixf)) + return GL_FALSE; + GLH_CORE_NAME(glLoadTransposeMatrixd) = (PFNGLLOADTRANSPOSEMATRIXDPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixd"); + if (NULL == GLH_CORE_NAME(glLoadTransposeMatrixd)) + return GL_FALSE; + GLH_CORE_NAME(glMultTransposeMatrixf) = (PFNGLMULTTRANSPOSEMATRIXFPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixf"); + if (NULL == GLH_CORE_NAME(glMultTransposeMatrixf)) + return GL_FALSE; + GLH_CORE_NAME(glMultTransposeMatrixd) = (PFNGLMULTTRANSPOSEMATRIXDPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixd"); + if (NULL == GLH_CORE_NAME(glMultTransposeMatrixd)) + return GL_FALSE; + GLH_CORE_NAME(glMultiDrawArrays) = (PFNGLMULTIDRAWARRAYSPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawArrays"); + if (NULL == GLH_CORE_NAME(glMultiDrawArrays)) + return GL_FALSE; + GLH_CORE_NAME(glMultiDrawElements) = (PFNGLMULTIDRAWELEMENTSPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawElements"); + if (NULL == GLH_CORE_NAME(glMultiDrawElements)) + return GL_FALSE; + GLH_CORE_NAME(glPointParameterf) = (PFNGLPOINTPARAMETERFPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterf"); + if (NULL == GLH_CORE_NAME(glPointParameterf)) + return GL_FALSE; + GLH_CORE_NAME(glPointParameterfv) = (PFNGLPOINTPARAMETERFVPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterfv"); + if (NULL == GLH_CORE_NAME(glPointParameterfv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3b) = (PFNGLSECONDARYCOLOR3BPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3b"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3b)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3bv) = (PFNGLSECONDARYCOLOR3BVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3bv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3bv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3d) = (PFNGLSECONDARYCOLOR3DPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3d"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3d)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3dv) = (PFNGLSECONDARYCOLOR3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3dv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3dv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3f) = (PFNGLSECONDARYCOLOR3FPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3f"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3f)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3fv) = (PFNGLSECONDARYCOLOR3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3fv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3fv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3i) = (PFNGLSECONDARYCOLOR3IPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3i"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3i)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3iv) = (PFNGLSECONDARYCOLOR3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3iv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3iv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3s) = (PFNGLSECONDARYCOLOR3SPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3s"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3s)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3sv) = (PFNGLSECONDARYCOLOR3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3sv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3sv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3ub) = (PFNGLSECONDARYCOLOR3UBPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ub"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3ub)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3ubv) = (PFNGLSECONDARYCOLOR3UBVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ubv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3ubv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3ui) = (PFNGLSECONDARYCOLOR3UIPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ui"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3ui)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3uiv) = (PFNGLSECONDARYCOLOR3UIVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3uiv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3uiv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3us) = (PFNGLSECONDARYCOLOR3USPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3us"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3us)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3usv) = (PFNGLSECONDARYCOLOR3USVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3usv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3usv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColorPointer) = (PFNGLSECONDARYCOLORPOINTERPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColorPointer"); + if (NULL == GLH_CORE_NAME(glSecondaryColorPointer)) + return GL_FALSE; + GLH_CORE_NAME(glBlendFuncSeparate) = (PFNGLBLENDFUNCSEPARATEPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendFuncSeparate"); + if (NULL == GLH_CORE_NAME(glBlendFuncSeparate)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2d) = (PFNGLWINDOWPOS2DPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2d"); + if (NULL == GLH_CORE_NAME(glWindowPos2d)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2f) = (PFNGLWINDOWPOS2FPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2f"); + if (NULL == GLH_CORE_NAME(glWindowPos2f)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2i) = (PFNGLWINDOWPOS2IPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2i"); + if (NULL == GLH_CORE_NAME(glWindowPos2i)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2s) = (PFNGLWINDOWPOS2SPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2s"); + if (NULL == GLH_CORE_NAME(glWindowPos2s)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2dv) = (PFNGLWINDOWPOS2DVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2dv"); + if (NULL == GLH_CORE_NAME(glWindowPos2dv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2fv) = (PFNGLWINDOWPOS2FVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2fv"); + if (NULL == GLH_CORE_NAME(glWindowPos2fv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2iv) = (PFNGLWINDOWPOS2IVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2iv"); + if (NULL == GLH_CORE_NAME(glWindowPos2iv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2sv) = (PFNGLWINDOWPOS2SVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2sv"); + if (NULL == GLH_CORE_NAME(glWindowPos2sv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3d) = (PFNGLWINDOWPOS3DPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3d"); + if (NULL == GLH_CORE_NAME(glWindowPos3d)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3f) = (PFNGLWINDOWPOS3FPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3f"); + if (NULL == GLH_CORE_NAME(glWindowPos3f)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3i) = (PFNGLWINDOWPOS3IPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3i"); + if (NULL == GLH_CORE_NAME(glWindowPos3i)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3s) = (PFNGLWINDOWPOS3SPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3s"); + if (NULL == GLH_CORE_NAME(glWindowPos3s)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3dv) = (PFNGLWINDOWPOS3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3dv"); + if (NULL == GLH_CORE_NAME(glWindowPos3dv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3fv) = (PFNGLWINDOWPOS3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3fv"); + if (NULL == GLH_CORE_NAME(glWindowPos3fv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3iv) = (PFNGLWINDOWPOS3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3iv"); + if (NULL == GLH_CORE_NAME(glWindowPos3iv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3sv) = (PFNGLWINDOWPOS3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3sv"); + if (NULL == GLH_CORE_NAME(glWindowPos3sv)) + return GL_FALSE; + + return GL_TRUE; + } +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_5) + if (0 == strcmp(extension, "GL_VERSION_1_5")) { + GLH_CORE_NAME(glBlendColor) = (PFNGLBLENDCOLORPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendColor"); + if (NULL == GLH_CORE_NAME(glBlendColor)) + return GL_FALSE; + GLH_CORE_NAME(glBlendEquation) = (PFNGLBLENDEQUATIONPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendEquation"); + if (NULL == GLH_CORE_NAME(glBlendEquation)) + return GL_FALSE; + GLH_CORE_NAME(glDrawRangeElements) = (PFNGLDRAWRANGEELEMENTSPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawRangeElements"); + if (NULL == GLH_CORE_NAME(glDrawRangeElements)) + return GL_FALSE; + GLH_CORE_NAME(glTexImage3D) = (PFNGLTEXIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexImage3D"); + if (NULL == GLH_CORE_NAME(glTexImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glTexSubImage3D) = (PFNGLTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCopyTexSubImage3D) = (PFNGLCOPYTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCopyTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glCopyTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1d) = (PFNGLMULTITEXCOORD1DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1dv) = (PFNGLMULTITEXCOORD1DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1f) = (PFNGLMULTITEXCOORD1FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1fv) = (PFNGLMULTITEXCOORD1FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1i) = (PFNGLMULTITEXCOORD1IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1iv) = (PFNGLMULTITEXCOORD1IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1s) = (PFNGLMULTITEXCOORD1SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord1sv) = (PFNGLMULTITEXCOORD1SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord1sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2d) = (PFNGLMULTITEXCOORD2DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2dv) = (PFNGLMULTITEXCOORD2DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2f) = (PFNGLMULTITEXCOORD2FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2fv) = (PFNGLMULTITEXCOORD2FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2i) = (PFNGLMULTITEXCOORD2IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2iv) = (PFNGLMULTITEXCOORD2IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2s) = (PFNGLMULTITEXCOORD2SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord2sv) = (PFNGLMULTITEXCOORD2SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord2sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3d) = (PFNGLMULTITEXCOORD3DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3dv) = (PFNGLMULTITEXCOORD3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3f) = (PFNGLMULTITEXCOORD3FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3fv) = (PFNGLMULTITEXCOORD3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3i) = (PFNGLMULTITEXCOORD3IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3iv) = (PFNGLMULTITEXCOORD3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3s) = (PFNGLMULTITEXCOORD3SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord3sv) = (PFNGLMULTITEXCOORD3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord3sv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4d) = (PFNGLMULTITEXCOORD4DPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4d"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4d)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4dv) = (PFNGLMULTITEXCOORD4DVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4dv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4dv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4f) = (PFNGLMULTITEXCOORD4FPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4f"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4f)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4fv) = (PFNGLMULTITEXCOORD4FVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4fv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4fv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4i) = (PFNGLMULTITEXCOORD4IPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4i"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4i)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4iv) = (PFNGLMULTITEXCOORD4IVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4iv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4iv)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4s) = (PFNGLMULTITEXCOORD4SPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4s"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4s)) + return GL_FALSE; + GLH_CORE_NAME(glMultiTexCoord4sv) = (PFNGLMULTITEXCOORD4SVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4sv"); + if (NULL == GLH_CORE_NAME(glMultiTexCoord4sv)) + return GL_FALSE; + GLH_CORE_NAME(glActiveTexture) = (PFNGLACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glActiveTexture"); + if (NULL == GLH_CORE_NAME(glActiveTexture)) + return GL_FALSE; + GLH_CORE_NAME(glClientActiveTexture) = (PFNGLCLIENTACTIVETEXTUREPROC)GLH_EXT_GET_PROC_ADDRESS("glClientActiveTexture"); + if (NULL == GLH_CORE_NAME(glClientActiveTexture)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage3D) = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage3D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage2D) = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage2D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage2D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexImage1D) = (PFNGLCOMPRESSEDTEXIMAGE1DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage1D"); + if (NULL == GLH_CORE_NAME(glCompressedTexImage1D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage3D) = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage3D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage3D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage2D) = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage2D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage2D)) + return GL_FALSE; + GLH_CORE_NAME(glCompressedTexSubImage1D) = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage1D"); + if (NULL == GLH_CORE_NAME(glCompressedTexSubImage1D)) + return GL_FALSE; + GLH_CORE_NAME(glGetCompressedTexImage) = (PFNGLGETCOMPRESSEDTEXIMAGEPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCompressedTexImage"); + if (NULL == GLH_CORE_NAME(glGetCompressedTexImage)) + return GL_FALSE; + GLH_CORE_NAME(glSampleCoverage) = (PFNGLSAMPLECOVERAGEPROC)GLH_EXT_GET_PROC_ADDRESS("glSampleCoverage"); + if (NULL == GLH_CORE_NAME(glSampleCoverage)) + return GL_FALSE; + GLH_CORE_NAME(glLoadTransposeMatrixf) = (PFNGLLOADTRANSPOSEMATRIXFPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixf"); + if (NULL == GLH_CORE_NAME(glLoadTransposeMatrixf)) + return GL_FALSE; + GLH_CORE_NAME(glLoadTransposeMatrixd) = (PFNGLLOADTRANSPOSEMATRIXDPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixd"); + if (NULL == GLH_CORE_NAME(glLoadTransposeMatrixd)) + return GL_FALSE; + GLH_CORE_NAME(glMultTransposeMatrixf) = (PFNGLMULTTRANSPOSEMATRIXFPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixf"); + if (NULL == GLH_CORE_NAME(glMultTransposeMatrixf)) + return GL_FALSE; + GLH_CORE_NAME(glMultTransposeMatrixd) = (PFNGLMULTTRANSPOSEMATRIXDPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixd"); + if (NULL == GLH_CORE_NAME(glMultTransposeMatrixd)) + return GL_FALSE; + GLH_CORE_NAME(glMultiDrawArrays) = (PFNGLMULTIDRAWARRAYSPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawArrays"); + if (NULL == GLH_CORE_NAME(glMultiDrawArrays)) + return GL_FALSE; + GLH_CORE_NAME(glMultiDrawElements) = (PFNGLMULTIDRAWELEMENTSPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawElements"); + if (NULL == GLH_CORE_NAME(glMultiDrawElements)) + return GL_FALSE; + GLH_CORE_NAME(glPointParameterf) = (PFNGLPOINTPARAMETERFPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterf"); + if (NULL == GLH_CORE_NAME(glPointParameterf)) + return GL_FALSE; + GLH_CORE_NAME(glPointParameterfv) = (PFNGLPOINTPARAMETERFVPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterfv"); + if (NULL == GLH_CORE_NAME(glPointParameterfv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3b) = (PFNGLSECONDARYCOLOR3BPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3b"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3b)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3bv) = (PFNGLSECONDARYCOLOR3BVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3bv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3bv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3d) = (PFNGLSECONDARYCOLOR3DPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3d"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3d)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3dv) = (PFNGLSECONDARYCOLOR3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3dv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3dv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3f) = (PFNGLSECONDARYCOLOR3FPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3f"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3f)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3fv) = (PFNGLSECONDARYCOLOR3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3fv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3fv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3i) = (PFNGLSECONDARYCOLOR3IPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3i"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3i)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3iv) = (PFNGLSECONDARYCOLOR3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3iv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3iv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3s) = (PFNGLSECONDARYCOLOR3SPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3s"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3s)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3sv) = (PFNGLSECONDARYCOLOR3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3sv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3sv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3ub) = (PFNGLSECONDARYCOLOR3UBPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ub"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3ub)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3ubv) = (PFNGLSECONDARYCOLOR3UBVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ubv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3ubv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3ui) = (PFNGLSECONDARYCOLOR3UIPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ui"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3ui)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3uiv) = (PFNGLSECONDARYCOLOR3UIVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3uiv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3uiv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3us) = (PFNGLSECONDARYCOLOR3USPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3us"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3us)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColor3usv) = (PFNGLSECONDARYCOLOR3USVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3usv"); + if (NULL == GLH_CORE_NAME(glSecondaryColor3usv)) + return GL_FALSE; + GLH_CORE_NAME(glSecondaryColorPointer) = (PFNGLSECONDARYCOLORPOINTERPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColorPointer"); + if (NULL == GLH_CORE_NAME(glSecondaryColorPointer)) + return GL_FALSE; + GLH_CORE_NAME(glBlendFuncSeparate) = (PFNGLBLENDFUNCSEPARATEPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendFuncSeparate"); + if (NULL == GLH_CORE_NAME(glBlendFuncSeparate)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2d) = (PFNGLWINDOWPOS2DPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2d"); + if (NULL == GLH_CORE_NAME(glWindowPos2d)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2f) = (PFNGLWINDOWPOS2FPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2f"); + if (NULL == GLH_CORE_NAME(glWindowPos2f)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2i) = (PFNGLWINDOWPOS2IPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2i"); + if (NULL == GLH_CORE_NAME(glWindowPos2i)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2s) = (PFNGLWINDOWPOS2SPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2s"); + if (NULL == GLH_CORE_NAME(glWindowPos2s)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2dv) = (PFNGLWINDOWPOS2DVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2dv"); + if (NULL == GLH_CORE_NAME(glWindowPos2dv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2fv) = (PFNGLWINDOWPOS2FVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2fv"); + if (NULL == GLH_CORE_NAME(glWindowPos2fv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2iv) = (PFNGLWINDOWPOS2IVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2iv"); + if (NULL == GLH_CORE_NAME(glWindowPos2iv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos2sv) = (PFNGLWINDOWPOS2SVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2sv"); + if (NULL == GLH_CORE_NAME(glWindowPos2sv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3d) = (PFNGLWINDOWPOS3DPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3d"); + if (NULL == GLH_CORE_NAME(glWindowPos3d)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3f) = (PFNGLWINDOWPOS3FPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3f"); + if (NULL == GLH_CORE_NAME(glWindowPos3f)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3i) = (PFNGLWINDOWPOS3IPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3i"); + if (NULL == GLH_CORE_NAME(glWindowPos3i)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3s) = (PFNGLWINDOWPOS3SPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3s"); + if (NULL == GLH_CORE_NAME(glWindowPos3s)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3dv) = (PFNGLWINDOWPOS3DVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3dv"); + if (NULL == GLH_CORE_NAME(glWindowPos3dv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3fv) = (PFNGLWINDOWPOS3FVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3fv"); + if (NULL == GLH_CORE_NAME(glWindowPos3fv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3iv) = (PFNGLWINDOWPOS3IVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3iv"); + if (NULL == GLH_CORE_NAME(glWindowPos3iv)) + return GL_FALSE; + GLH_CORE_NAME(glWindowPos3sv) = (PFNGLWINDOWPOS3SVPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3sv"); + if (NULL == GLH_CORE_NAME(glWindowPos3sv)) + return GL_FALSE; + GLH_CORE_NAME(glGenQueries) = (PFNGLGENQUERIESPROC)GLH_EXT_GET_PROC_ADDRESS("glGenQueries"); + if (NULL == GLH_CORE_NAME(glGenQueries)) + return GL_FALSE; + GLH_CORE_NAME(glDeleteQueries) = (PFNGLDELETEQUERIESPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteQueries"); + if (NULL == GLH_CORE_NAME(glDeleteQueries)) + return GL_FALSE; + GLH_CORE_NAME(glIsQuery) = (PFNGLISQUERYPROC)GLH_EXT_GET_PROC_ADDRESS("glIsQuery"); + if (NULL == GLH_CORE_NAME(glIsQuery)) + return GL_FALSE; + GLH_CORE_NAME(glBeginQuery) = (PFNGLBEGINQUERYPROC)GLH_EXT_GET_PROC_ADDRESS("glBeginQuery"); + if (NULL == GLH_CORE_NAME(glBeginQuery)) + return GL_FALSE; + GLH_CORE_NAME(glEndQuery) = (PFNGLENDQUERYPROC)GLH_EXT_GET_PROC_ADDRESS("glEndQuery"); + if (NULL == GLH_CORE_NAME(glEndQuery)) + return GL_FALSE; + GLH_CORE_NAME(glGetQueryiv) = (PFNGLGETQUERYIVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetQueryiv"); + if (NULL == GLH_CORE_NAME(glGetQueryiv)) + return GL_FALSE; + GLH_CORE_NAME(glGetQueryObjectiv) = (PFNGLGETQUERYOBJECTIVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetQueryObjectiv"); + if (NULL == GLH_CORE_NAME(glGetQueryObjectiv)) + return GL_FALSE; + GLH_CORE_NAME(glGetQueryObjectuiv) = (PFNGLGETQUERYOBJECTUIVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetQueryObjectuiv"); + if (NULL == GLH_CORE_NAME(glGetQueryObjectuiv)) + return GL_FALSE; + GLH_CORE_NAME(glBindBuffer) = (PFNGLBINDBUFFERPROC)GLH_EXT_GET_PROC_ADDRESS("glBindBuffer"); + if (NULL == GLH_CORE_NAME(glBindBuffer)) + return GL_FALSE; + GLH_CORE_NAME(glDeleteBuffers) = (PFNGLDELETEBUFFERSPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteBuffers"); + if (NULL == GLH_CORE_NAME(glDeleteBuffers)) + return GL_FALSE; + GLH_CORE_NAME(glGenBuffers) = (PFNGLGENBUFFERSPROC)GLH_EXT_GET_PROC_ADDRESS("glGenBuffers"); + if (NULL == GLH_CORE_NAME(glGenBuffers)) + return GL_FALSE; + GLH_CORE_NAME(glIsBuffer) = (PFNGLISBUFFERPROC)GLH_EXT_GET_PROC_ADDRESS("glIsBuffer"); + if (NULL == GLH_CORE_NAME(glIsBuffer)) + return GL_FALSE; + GLH_CORE_NAME(glBufferData) = (PFNGLBUFFERDATAPROC)GLH_EXT_GET_PROC_ADDRESS("glBufferData"); + if (NULL == GLH_CORE_NAME(glBufferData)) + return GL_FALSE; + GLH_CORE_NAME(glBufferSubData) = (PFNGLBUFFERSUBDATAPROC)GLH_EXT_GET_PROC_ADDRESS("glBufferSubData"); + if (NULL == GLH_CORE_NAME(glBufferSubData)) + return GL_FALSE; + GLH_CORE_NAME(glGetBufferSubData) = (PFNGLGETBUFFERSUBDATAPROC)GLH_EXT_GET_PROC_ADDRESS("glGetBufferSubData"); + if (NULL == GLH_CORE_NAME(glGetBufferSubData)) + return GL_FALSE; + GLH_CORE_NAME(glMapBuffer) = (PFNGLMAPBUFFERPROC)GLH_EXT_GET_PROC_ADDRESS("glMapBuffer"); + if (NULL == GLH_CORE_NAME(glMapBuffer)) + return GL_FALSE; + GLH_CORE_NAME(glUnmapBuffer) = (PFNGLUNMAPBUFFERPROC)GLH_EXT_GET_PROC_ADDRESS("glUnmapBuffer"); + if (NULL == GLH_CORE_NAME(glUnmapBuffer)) + return GL_FALSE; + GLH_CORE_NAME(glGetBufferParameteriv) = (PFNGLGETBUFFERPARAMETERIVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetBufferParameteriv"); + if (NULL == GLH_CORE_NAME(glGetBufferParameteriv)) + return GL_FALSE; + GLH_CORE_NAME(glGetBufferPointerv) = (PFNGLGETBUFFERPOINTERVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetBufferPointerv"); + if (NULL == GLH_CORE_NAME(glGetBufferPointerv)) + return GL_FALSE; + + return GL_TRUE; + } +#endif +#endif + +#ifdef GL_ARB_depth_texture + if (0 == strcmp(extension, "GL_ARB_depth_texture")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_fragment_program + if (0 == strcmp(extension, "GL_ARB_fragment_program")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_fragment_program_shadow + if (0 == strcmp(extension, "GL_ARB_fragment_program_shadow")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_fragment_shader + if (0 == strcmp(extension, "GL_ARB_fragment_shader")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_matrix_palette + if (0 == strcmp(extension, "GL_ARB_matrix_palette")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_multisample + if (0 == strcmp(extension, "GL_ARB_multisample")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_multitexture + if (0 == strcmp(extension, "GL_ARB_multitexture")) { + GLH_EXT_NAME(glMultiTexCoord1dARB) = (PFNGLMULTITEXCOORD1DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1dARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1dARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1dvARB) = (PFNGLMULTITEXCOORD1DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1dvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1fARB) = (PFNGLMULTITEXCOORD1FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1fARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1fARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1fvARB) = (PFNGLMULTITEXCOORD1FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1fvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1iARB) = (PFNGLMULTITEXCOORD1IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1iARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1iARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1ivARB) = (PFNGLMULTITEXCOORD1IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1ivARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1sARB) = (PFNGLMULTITEXCOORD1SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1sARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1sARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1svARB) = (PFNGLMULTITEXCOORD1SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1svARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1svARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2dARB) = (PFNGLMULTITEXCOORD2DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2dARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2dARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2dvARB) = (PFNGLMULTITEXCOORD2DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2dvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2fARB) = (PFNGLMULTITEXCOORD2FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2fARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2fARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2fvARB) = (PFNGLMULTITEXCOORD2FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2fvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2iARB) = (PFNGLMULTITEXCOORD2IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2iARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2iARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2ivARB) = (PFNGLMULTITEXCOORD2IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2ivARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2sARB) = (PFNGLMULTITEXCOORD2SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2sARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2sARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2svARB) = (PFNGLMULTITEXCOORD2SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2svARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2svARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3dARB) = (PFNGLMULTITEXCOORD3DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3dARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3dARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3dvARB) = (PFNGLMULTITEXCOORD3DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3dvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3fARB) = (PFNGLMULTITEXCOORD3FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3fARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3fARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3fvARB) = (PFNGLMULTITEXCOORD3FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3fvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3iARB) = (PFNGLMULTITEXCOORD3IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3iARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3iARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3ivARB) = (PFNGLMULTITEXCOORD3IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3ivARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3sARB) = (PFNGLMULTITEXCOORD3SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3sARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3sARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3svARB) = (PFNGLMULTITEXCOORD3SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3svARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3svARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4dARB) = (PFNGLMULTITEXCOORD4DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4dARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4dARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4dvARB) = (PFNGLMULTITEXCOORD4DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4dvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4fARB) = (PFNGLMULTITEXCOORD4FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4fARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4fARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4fvARB) = (PFNGLMULTITEXCOORD4FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4fvARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4iARB) = (PFNGLMULTITEXCOORD4IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4iARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4iARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4ivARB) = (PFNGLMULTITEXCOORD4IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4ivARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4sARB) = (PFNGLMULTITEXCOORD4SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4sARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4sARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4svARB) = (PFNGLMULTITEXCOORD4SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4svARB"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4svARB)) + return GL_FALSE; + GLH_EXT_NAME(glActiveTextureARB) = (PFNGLACTIVETEXTUREARBPROC)GLH_EXT_GET_PROC_ADDRESS("glActiveTextureARB"); + if (NULL == GLH_EXT_NAME(glActiveTextureARB)) + return GL_FALSE; + GLH_EXT_NAME(glClientActiveTextureARB) = (PFNGLCLIENTACTIVETEXTUREARBPROC)GLH_EXT_GET_PROC_ADDRESS("glClientActiveTextureARB"); + if (NULL == GLH_EXT_NAME(glClientActiveTextureARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_occlusion_query + if (0 == strcmp(extension, "GL_ARB_occlusion_query")) { + GLH_EXT_NAME(glGenQueriesARB) = (PFNGLGENQUERIESARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGenQueriesARB"); + if (NULL == GLH_EXT_NAME(glGenQueriesARB)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteQueriesARB) = (PFNGLDELETEQUERIESARBPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteQueriesARB"); + if (NULL == GLH_EXT_NAME(glDeleteQueriesARB)) + return GL_FALSE; + GLH_EXT_NAME(glIsQueryARB) = (PFNGLISQUERYARBPROC)GLH_EXT_GET_PROC_ADDRESS("glIsQueryARB"); + if (NULL == GLH_EXT_NAME(glIsQueryARB)) + return GL_FALSE; + GLH_EXT_NAME(glBeginQueryARB) = (PFNGLBEGINQUERYARBPROC)GLH_EXT_GET_PROC_ADDRESS("glBeginQueryARB"); + if (NULL == GLH_EXT_NAME(glBeginQueryARB)) + return GL_FALSE; + GLH_EXT_NAME(glEndQueryARB) = (PFNGLENDQUERYARBPROC)GLH_EXT_GET_PROC_ADDRESS("glEndQueryARB"); + if (NULL == GLH_EXT_NAME(glEndQueryARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetQueryivARB) = (PFNGLGETQUERYIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetQueryivARB"); + if (NULL == GLH_EXT_NAME(glGetQueryivARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetQueryObjectivARB) = (PFNGLGETQUERYOBJECTIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetQueryObjectivARB"); + if (NULL == GLH_EXT_NAME(glGetQueryObjectivARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetQueryObjectuivARB) = (PFNGLGETQUERYOBJECTUIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetQueryObjectuivARB"); + if (NULL == GLH_EXT_NAME(glGetQueryObjectuivARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_point_parameters + if (0 == strcmp(extension, "GL_ARB_point_parameters")) { + GLH_EXT_NAME(glPointParameterfARB) = (PFNGLPOINTPARAMETERFARBPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterfARB"); + if (NULL == GLH_EXT_NAME(glPointParameterfARB)) + return GL_FALSE; + GLH_EXT_NAME(glPointParameterfvARB) = (PFNGLPOINTPARAMETERFVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterfvARB"); + if (NULL == GLH_EXT_NAME(glPointParameterfvARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_point_sprite + if (0 == strcmp(extension, "GL_ARB_point_sprite")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_shader_objects + if (0 == strcmp(extension, "GL_ARB_shader_objects")) { + GLH_EXT_NAME(glDeleteObjectARB) = (PFNGLDELETEOBJECTARBPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteObjectARB"); + if (NULL == GLH_EXT_NAME(glDeleteObjectARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetHandleARB) = (PFNGLGETHANDLEARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetHandleARB"); + if (NULL == GLH_EXT_NAME(glGetHandleARB)) + return GL_FALSE; + GLH_EXT_NAME(glDetachObjectARB) = (PFNGLDETACHOBJECTARBPROC)GLH_EXT_GET_PROC_ADDRESS("glDetachObjectARB"); + if (NULL == GLH_EXT_NAME(glDetachObjectARB)) + return GL_FALSE; + GLH_EXT_NAME(glCreateShaderObjectARB) = (PFNGLCREATESHADEROBJECTARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCreateShaderObjectARB"); + if (NULL == GLH_EXT_NAME(glCreateShaderObjectARB)) + return GL_FALSE; + GLH_EXT_NAME(glShaderSourceARB) = (PFNGLSHADERSOURCEARBPROC)GLH_EXT_GET_PROC_ADDRESS("glShaderSourceARB"); + if (NULL == GLH_EXT_NAME(glShaderSourceARB)) + return GL_FALSE; + GLH_EXT_NAME(glCompileShaderARB) = (PFNGLCOMPILESHADERARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCompileShaderARB"); + if (NULL == GLH_EXT_NAME(glCompileShaderARB)) + return GL_FALSE; + GLH_EXT_NAME(glCreateProgramObjectARB) = (PFNGLCREATEPROGRAMOBJECTARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCreateProgramObjectARB"); + if (NULL == GLH_EXT_NAME(glCreateProgramObjectARB)) + return GL_FALSE; + GLH_EXT_NAME(glAttachObjectARB) = (PFNGLATTACHOBJECTARBPROC)GLH_EXT_GET_PROC_ADDRESS("glAttachObjectARB"); + if (NULL == GLH_EXT_NAME(glAttachObjectARB)) + return GL_FALSE; + GLH_EXT_NAME(glLinkProgramARB) = (PFNGLLINKPROGRAMARBPROC)GLH_EXT_GET_PROC_ADDRESS("glLinkProgramARB"); + if (NULL == GLH_EXT_NAME(glLinkProgramARB)) + return GL_FALSE; + GLH_EXT_NAME(glUseProgramObjectARB) = (PFNGLUSEPROGRAMOBJECTARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUseProgramObjectARB"); + if (NULL == GLH_EXT_NAME(glUseProgramObjectARB)) + return GL_FALSE; + GLH_EXT_NAME(glValidateProgramARB) = (PFNGLVALIDATEPROGRAMARBPROC)GLH_EXT_GET_PROC_ADDRESS("glValidateProgramARB"); + if (NULL == GLH_EXT_NAME(glValidateProgramARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform1fARB) = (PFNGLUNIFORM1FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform1fARB"); + if (NULL == GLH_EXT_NAME(glUniform1fARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform2fARB) = (PFNGLUNIFORM2FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform2fARB"); + if (NULL == GLH_EXT_NAME(glUniform2fARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform3fARB) = (PFNGLUNIFORM3FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform3fARB"); + if (NULL == GLH_EXT_NAME(glUniform3fARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform4fARB) = (PFNGLUNIFORM4FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform4fARB"); + if (NULL == GLH_EXT_NAME(glUniform4fARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform1iARB) = (PFNGLUNIFORM1IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform1iARB"); + if (NULL == GLH_EXT_NAME(glUniform1iARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform2iARB) = (PFNGLUNIFORM2IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform2iARB"); + if (NULL == GLH_EXT_NAME(glUniform2iARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform3iARB) = (PFNGLUNIFORM3IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform3iARB"); + if (NULL == GLH_EXT_NAME(glUniform3iARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform4iARB) = (PFNGLUNIFORM4IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform4iARB"); + if (NULL == GLH_EXT_NAME(glUniform4iARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform1fvARB) = (PFNGLUNIFORM1FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform1fvARB"); + if (NULL == GLH_EXT_NAME(glUniform1fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform2fvARB) = (PFNGLUNIFORM2FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform2fvARB"); + if (NULL == GLH_EXT_NAME(glUniform2fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform3fvARB) = (PFNGLUNIFORM3FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform3fvARB"); + if (NULL == GLH_EXT_NAME(glUniform3fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform4fvARB) = (PFNGLUNIFORM4FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform4fvARB"); + if (NULL == GLH_EXT_NAME(glUniform4fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform1ivARB) = (PFNGLUNIFORM1IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform1ivARB"); + if (NULL == GLH_EXT_NAME(glUniform1ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform2ivARB) = (PFNGLUNIFORM2IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform2ivARB"); + if (NULL == GLH_EXT_NAME(glUniform2ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform3ivARB) = (PFNGLUNIFORM3IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform3ivARB"); + if (NULL == GLH_EXT_NAME(glUniform3ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniform4ivARB) = (PFNGLUNIFORM4IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniform4ivARB"); + if (NULL == GLH_EXT_NAME(glUniform4ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniformMatrix2fvARB) = (PFNGLUNIFORMMATRIX2FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniformMatrix2fvARB"); + if (NULL == GLH_EXT_NAME(glUniformMatrix2fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniformMatrix3fvARB) = (PFNGLUNIFORMMATRIX3FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniformMatrix3fvARB"); + if (NULL == GLH_EXT_NAME(glUniformMatrix3fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glUniformMatrix4fvARB) = (PFNGLUNIFORMMATRIX4FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUniformMatrix4fvARB"); + if (NULL == GLH_EXT_NAME(glUniformMatrix4fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetObjectParameterfvARB) = (PFNGLGETOBJECTPARAMETERFVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetObjectParameterfvARB"); + if (NULL == GLH_EXT_NAME(glGetObjectParameterfvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetObjectParameterivARB) = (PFNGLGETOBJECTPARAMETERIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetObjectParameterivARB"); + if (NULL == GLH_EXT_NAME(glGetObjectParameterivARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetInfoLogARB) = (PFNGLGETINFOLOGARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetInfoLogARB"); + if (NULL == GLH_EXT_NAME(glGetInfoLogARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetAttachedObjectsARB) = (PFNGLGETATTACHEDOBJECTSARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetAttachedObjectsARB"); + if (NULL == GLH_EXT_NAME(glGetAttachedObjectsARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetUniformLocationARB) = (PFNGLGETUNIFORMLOCATIONARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetUniformLocationARB"); + if (NULL == GLH_EXT_NAME(glGetUniformLocationARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetActiveUniformARB) = (PFNGLGETACTIVEUNIFORMARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetActiveUniformARB"); + if (NULL == GLH_EXT_NAME(glGetActiveUniformARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetUniformfvARB) = (PFNGLGETUNIFORMFVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetUniformfvARB"); + if (NULL == GLH_EXT_NAME(glGetUniformfvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetUniformivARB) = (PFNGLGETUNIFORMIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetUniformivARB"); + if (NULL == GLH_EXT_NAME(glGetUniformivARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetShaderSourceARB) = (PFNGLGETSHADERSOURCEARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetShaderSourceARB"); + if (NULL == GLH_EXT_NAME(glGetShaderSourceARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_shadow + if (0 == strcmp(extension, "GL_ARB_shadow")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_shadow_ambient + if (0 == strcmp(extension, "GL_ARB_shadow_ambient")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_border_clamp + if (0 == strcmp(extension, "GL_ARB_texture_border_clamp")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_compression + if (0 == strcmp(extension, "GL_ARB_texture_compression")) { + GLH_EXT_NAME(glCompressedTexImage3DARB) = (PFNGLCOMPRESSEDTEXIMAGE3DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage3DARB"); + if (NULL == GLH_EXT_NAME(glCompressedTexImage3DARB)) + return GL_FALSE; + GLH_EXT_NAME(glCompressedTexImage2DARB) = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage2DARB"); + if (NULL == GLH_EXT_NAME(glCompressedTexImage2DARB)) + return GL_FALSE; + GLH_EXT_NAME(glCompressedTexImage1DARB) = (PFNGLCOMPRESSEDTEXIMAGE1DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexImage1DARB"); + if (NULL == GLH_EXT_NAME(glCompressedTexImage1DARB)) + return GL_FALSE; + GLH_EXT_NAME(glCompressedTexSubImage3DARB) = (PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage3DARB"); + if (NULL == GLH_EXT_NAME(glCompressedTexSubImage3DARB)) + return GL_FALSE; + GLH_EXT_NAME(glCompressedTexSubImage2DARB) = (PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage2DARB"); + if (NULL == GLH_EXT_NAME(glCompressedTexSubImage2DARB)) + return GL_FALSE; + GLH_EXT_NAME(glCompressedTexSubImage1DARB) = (PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glCompressedTexSubImage1DARB"); + if (NULL == GLH_EXT_NAME(glCompressedTexSubImage1DARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetCompressedTexImageARB) = (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCompressedTexImageARB"); + if (NULL == GLH_EXT_NAME(glGetCompressedTexImageARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_cube_map + if (0 == strcmp(extension, "GL_ARB_texture_cube_map")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_env_add + if (0 == strcmp(extension, "GL_ARB_texture_env_add")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_env_combine + if (0 == strcmp(extension, "GL_ARB_texture_env_combine")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_env_dot3 + if (0 == strcmp(extension, "GL_ARB_texture_env_dot3")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_mirrored_repeat + if (0 == strcmp(extension, "GL_ARB_texture_mirrored_repeat")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_non_power_of_two + if (0 == strcmp(extension, "GL_ARB_texture_non_power_of_two")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_texture_rectangle + if (0 == strcmp(extension, "GL_ARB_texture_rectangle")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_transpose_matrix + if (0 == strcmp(extension, "GL_ARB_transpose_matrix")) { + GLH_EXT_NAME(glLoadTransposeMatrixfARB) = (PFNGLLOADTRANSPOSEMATRIXFARBPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixfARB"); + if (NULL == GLH_EXT_NAME(glLoadTransposeMatrixfARB)) + return GL_FALSE; + GLH_EXT_NAME(glLoadTransposeMatrixdARB) = (PFNGLLOADTRANSPOSEMATRIXDARBPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadTransposeMatrixdARB"); + if (NULL == GLH_EXT_NAME(glLoadTransposeMatrixdARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultTransposeMatrixfARB) = (PFNGLMULTTRANSPOSEMATRIXFARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixfARB"); + if (NULL == GLH_EXT_NAME(glMultTransposeMatrixfARB)) + return GL_FALSE; + GLH_EXT_NAME(glMultTransposeMatrixdARB) = (PFNGLMULTTRANSPOSEMATRIXDARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMultTransposeMatrixdARB"); + if (NULL == GLH_EXT_NAME(glMultTransposeMatrixdARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_vertex_buffer_object + if (0 == strcmp(extension, "GL_ARB_vertex_buffer_object")) { + GLH_EXT_NAME(glBindBufferARB) = (PFNGLBINDBUFFERARBPROC)GLH_EXT_GET_PROC_ADDRESS("glBindBufferARB"); + if (NULL == GLH_EXT_NAME(glBindBufferARB)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteBuffersARB) = (PFNGLDELETEBUFFERSARBPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteBuffersARB"); + if (NULL == GLH_EXT_NAME(glDeleteBuffersARB)) + return GL_FALSE; + GLH_EXT_NAME(glGenBuffersARB) = (PFNGLGENBUFFERSARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGenBuffersARB"); + if (NULL == GLH_EXT_NAME(glGenBuffersARB)) + return GL_FALSE; + GLH_EXT_NAME(glIsBufferARB) = (PFNGLISBUFFERARBPROC)GLH_EXT_GET_PROC_ADDRESS("glIsBufferARB"); + if (NULL == GLH_EXT_NAME(glIsBufferARB)) + return GL_FALSE; + GLH_EXT_NAME(glBufferDataARB) = (PFNGLBUFFERDATAARBPROC)GLH_EXT_GET_PROC_ADDRESS("glBufferDataARB"); + if (NULL == GLH_EXT_NAME(glBufferDataARB)) + return GL_FALSE; + GLH_EXT_NAME(glBufferSubDataARB) = (PFNGLBUFFERSUBDATAARBPROC)GLH_EXT_GET_PROC_ADDRESS("glBufferSubDataARB"); + if (NULL == GLH_EXT_NAME(glBufferSubDataARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetBufferSubDataARB) = (PFNGLGETBUFFERSUBDATAARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetBufferSubDataARB"); + if (NULL == GLH_EXT_NAME(glGetBufferSubDataARB)) + return GL_FALSE; + GLH_EXT_NAME(glMapBufferARB) = (PFNGLMAPBUFFERARBPROC)GLH_EXT_GET_PROC_ADDRESS("glMapBufferARB"); + if (NULL == GLH_EXT_NAME(glMapBufferARB)) + return GL_FALSE; + GLH_EXT_NAME(glUnmapBufferARB) = (PFNGLUNMAPBUFFERARBPROC)GLH_EXT_GET_PROC_ADDRESS("glUnmapBufferARB"); + if (NULL == GLH_EXT_NAME(glUnmapBufferARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetBufferParameterivARB) = (PFNGLGETBUFFERPARAMETERIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetBufferParameterivARB"); + if (NULL == GLH_EXT_NAME(glGetBufferParameterivARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetBufferPointervARB) = (PFNGLGETBUFFERPOINTERVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetBufferPointervARB"); + if (NULL == GLH_EXT_NAME(glGetBufferPointervARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_vertex_program + if (0 == strcmp(extension, "GL_ARB_vertex_program")) { + GLH_EXT_NAME(glVertexAttrib1sARB) = (PFNGLVERTEXATTRIB1SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1sARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1sARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1fARB) = (PFNGLVERTEXATTRIB1FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1fARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1fARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1dARB) = (PFNGLVERTEXATTRIB1DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1dARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1dARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2sARB) = (PFNGLVERTEXATTRIB2SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2sARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2sARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2fARB) = (PFNGLVERTEXATTRIB2FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2fARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2fARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2dARB) = (PFNGLVERTEXATTRIB2DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2dARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2dARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3sARB) = (PFNGLVERTEXATTRIB3SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3sARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3sARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3fARB) = (PFNGLVERTEXATTRIB3FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3fARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3fARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3dARB) = (PFNGLVERTEXATTRIB3DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3dARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3dARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4sARB) = (PFNGLVERTEXATTRIB4SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4sARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4sARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4fARB) = (PFNGLVERTEXATTRIB4FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4fARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4fARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4dARB) = (PFNGLVERTEXATTRIB4DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4dARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4dARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4NubARB) = (PFNGLVERTEXATTRIB4NUBARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4NubARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4NubARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1svARB) = (PFNGLVERTEXATTRIB1SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1svARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1svARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1fvARB) = (PFNGLVERTEXATTRIB1FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1fvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1dvARB) = (PFNGLVERTEXATTRIB1DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1dvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2svARB) = (PFNGLVERTEXATTRIB2SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2svARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2svARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2fvARB) = (PFNGLVERTEXATTRIB2FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2fvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2dvARB) = (PFNGLVERTEXATTRIB2DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2dvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3svARB) = (PFNGLVERTEXATTRIB3SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3svARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3svARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3fvARB) = (PFNGLVERTEXATTRIB3FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3fvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3dvARB) = (PFNGLVERTEXATTRIB3DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3dvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4bvARB) = (PFNGLVERTEXATTRIB4BVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4bvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4bvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4svARB) = (PFNGLVERTEXATTRIB4SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4svARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4svARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4ivARB) = (PFNGLVERTEXATTRIB4IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4ivARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4ubvARB) = (PFNGLVERTEXATTRIB4UBVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4ubvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4ubvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4usvARB) = (PFNGLVERTEXATTRIB4USVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4usvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4usvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4uivARB) = (PFNGLVERTEXATTRIB4UIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4uivARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4uivARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4fvARB) = (PFNGLVERTEXATTRIB4FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4fvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4dvARB) = (PFNGLVERTEXATTRIB4DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4dvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4NbvARB) = (PFNGLVERTEXATTRIB4NBVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4NbvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4NbvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4NsvARB) = (PFNGLVERTEXATTRIB4NSVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4NsvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4NsvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4NivARB) = (PFNGLVERTEXATTRIB4NIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4NivARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4NivARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4NubvARB) = (PFNGLVERTEXATTRIB4NUBVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4NubvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4NubvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4NusvARB) = (PFNGLVERTEXATTRIB4NUSVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4NusvARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4NusvARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4NuivARB) = (PFNGLVERTEXATTRIB4NUIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4NuivARB"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4NuivARB)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribPointerARB) = (PFNGLVERTEXATTRIBPOINTERARBPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribPointerARB"); + if (NULL == GLH_EXT_NAME(glVertexAttribPointerARB)) + return GL_FALSE; + GLH_EXT_NAME(glEnableVertexAttribArrayARB) = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)GLH_EXT_GET_PROC_ADDRESS("glEnableVertexAttribArrayARB"); + if (NULL == GLH_EXT_NAME(glEnableVertexAttribArrayARB)) + return GL_FALSE; + GLH_EXT_NAME(glDisableVertexAttribArrayARB) = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)GLH_EXT_GET_PROC_ADDRESS("glDisableVertexAttribArrayARB"); + if (NULL == GLH_EXT_NAME(glDisableVertexAttribArrayARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramStringARB) = (PFNGLPROGRAMSTRINGARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramStringARB"); + if (NULL == GLH_EXT_NAME(glProgramStringARB)) + return GL_FALSE; + GLH_EXT_NAME(glBindProgramARB) = (PFNGLBINDPROGRAMARBPROC)GLH_EXT_GET_PROC_ADDRESS("glBindProgramARB"); + if (NULL == GLH_EXT_NAME(glBindProgramARB)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteProgramsARB) = (PFNGLDELETEPROGRAMSARBPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteProgramsARB"); + if (NULL == GLH_EXT_NAME(glDeleteProgramsARB)) + return GL_FALSE; + GLH_EXT_NAME(glGenProgramsARB) = (PFNGLGENPROGRAMSARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGenProgramsARB"); + if (NULL == GLH_EXT_NAME(glGenProgramsARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramEnvParameter4dARB) = (PFNGLPROGRAMENVPARAMETER4DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramEnvParameter4dARB"); + if (NULL == GLH_EXT_NAME(glProgramEnvParameter4dARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramEnvParameter4dvARB) = (PFNGLPROGRAMENVPARAMETER4DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramEnvParameter4dvARB"); + if (NULL == GLH_EXT_NAME(glProgramEnvParameter4dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramEnvParameter4fARB) = (PFNGLPROGRAMENVPARAMETER4FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramEnvParameter4fARB"); + if (NULL == GLH_EXT_NAME(glProgramEnvParameter4fARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramEnvParameter4fvARB) = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramEnvParameter4fvARB"); + if (NULL == GLH_EXT_NAME(glProgramEnvParameter4fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramLocalParameter4dARB) = (PFNGLPROGRAMLOCALPARAMETER4DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramLocalParameter4dARB"); + if (NULL == GLH_EXT_NAME(glProgramLocalParameter4dARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramLocalParameter4dvARB) = (PFNGLPROGRAMLOCALPARAMETER4DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramLocalParameter4dvARB"); + if (NULL == GLH_EXT_NAME(glProgramLocalParameter4dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramLocalParameter4fARB) = (PFNGLPROGRAMLOCALPARAMETER4FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramLocalParameter4fARB"); + if (NULL == GLH_EXT_NAME(glProgramLocalParameter4fARB)) + return GL_FALSE; + GLH_EXT_NAME(glProgramLocalParameter4fvARB) = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramLocalParameter4fvARB"); + if (NULL == GLH_EXT_NAME(glProgramLocalParameter4fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramEnvParameterdvARB) = (PFNGLGETPROGRAMENVPARAMETERDVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramEnvParameterdvARB"); + if (NULL == GLH_EXT_NAME(glGetProgramEnvParameterdvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramEnvParameterfvARB) = (PFNGLGETPROGRAMENVPARAMETERFVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramEnvParameterfvARB"); + if (NULL == GLH_EXT_NAME(glGetProgramEnvParameterfvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramLocalParameterdvARB) = (PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramLocalParameterdvARB"); + if (NULL == GLH_EXT_NAME(glGetProgramLocalParameterdvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramLocalParameterfvARB) = (PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramLocalParameterfvARB"); + if (NULL == GLH_EXT_NAME(glGetProgramLocalParameterfvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramivARB) = (PFNGLGETPROGRAMIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramivARB"); + if (NULL == GLH_EXT_NAME(glGetProgramivARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramStringARB) = (PFNGLGETPROGRAMSTRINGARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramStringARB"); + if (NULL == GLH_EXT_NAME(glGetProgramStringARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribdvARB) = (PFNGLGETVERTEXATTRIBDVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribdvARB"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribdvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribfvARB) = (PFNGLGETVERTEXATTRIBFVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribfvARB"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribfvARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribivARB) = (PFNGLGETVERTEXATTRIBIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribivARB"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribivARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribPointervARB) = (PFNGLGETVERTEXATTRIBPOINTERVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribPointervARB"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribPointervARB)) + return GL_FALSE; + GLH_EXT_NAME(glIsProgramARB) = (PFNGLISPROGRAMARBPROC)GLH_EXT_GET_PROC_ADDRESS("glIsProgramARB"); + if (NULL == GLH_EXT_NAME(glIsProgramARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_vertex_shader + if (0 == strcmp(extension, "GL_ARB_vertex_shader")) { + GLH_EXT_NAME(glBindAttribLocationARB) = (PFNGLBINDATTRIBLOCATIONARBPROC)GLH_EXT_GET_PROC_ADDRESS("glBindAttribLocationARB"); + if (NULL == GLH_EXT_NAME(glBindAttribLocationARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetActiveAttribARB) = (PFNGLGETACTIVEATTRIBARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetActiveAttribARB"); + if (NULL == GLH_EXT_NAME(glGetActiveAttribARB)) + return GL_FALSE; + GLH_EXT_NAME(glGetAttribLocationARB) = (PFNGLGETATTRIBLOCATIONARBPROC)GLH_EXT_GET_PROC_ADDRESS("glGetAttribLocationARB"); + if (NULL == GLH_EXT_NAME(glGetAttribLocationARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ARB_window_pos + if (0 == strcmp(extension, "GL_ARB_window_pos")) { + GLH_EXT_NAME(glWindowPos2dARB) = (PFNGLWINDOWPOS2DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2dARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2dARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos2fARB) = (PFNGLWINDOWPOS2FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2fARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2fARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos2iARB) = (PFNGLWINDOWPOS2IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2iARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2iARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos2sARB) = (PFNGLWINDOWPOS2SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2sARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2sARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos2dvARB) = (PFNGLWINDOWPOS2DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2dvARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos2fvARB) = (PFNGLWINDOWPOS2FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2fvARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos2ivARB) = (PFNGLWINDOWPOS2IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2ivARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos2svARB) = (PFNGLWINDOWPOS2SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos2svARB"); + if (NULL == GLH_EXT_NAME(glWindowPos2svARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3dARB) = (PFNGLWINDOWPOS3DARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3dARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3dARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3fARB) = (PFNGLWINDOWPOS3FARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3fARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3fARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3iARB) = (PFNGLWINDOWPOS3IARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3iARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3iARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3sARB) = (PFNGLWINDOWPOS3SARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3sARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3sARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3dvARB) = (PFNGLWINDOWPOS3DVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3dvARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3dvARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3fvARB) = (PFNGLWINDOWPOS3FVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3fvARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3fvARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3ivARB) = (PFNGLWINDOWPOS3IVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3ivARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3ivARB)) + return GL_FALSE; + GLH_EXT_NAME(glWindowPos3svARB) = (PFNGLWINDOWPOS3SVARBPROC)GLH_EXT_GET_PROC_ADDRESS("glWindowPos3svARB"); + if (NULL == GLH_EXT_NAME(glWindowPos3svARB)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ATI_draw_buffers + if (0 == strcmp(extension, "GL_ATI_draw_buffers")) { + GLH_EXT_NAME(glDrawBuffersATI) = (PFNGLDRAWBUFFERSATIPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawBuffersATI"); + if (NULL == GLH_EXT_NAME(glDrawBuffersATI)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_ATI_texture_float + if (0 == strcmp(extension, "GL_ATI_texture_float")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_abgr + if (0 == strcmp(extension, "GL_EXT_abgr")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_bgra + if (0 == strcmp(extension, "GL_EXT_bgra")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_blend_color + if (0 == strcmp(extension, "GL_EXT_blend_color")) { + GLH_EXT_NAME(glBlendColorEXT) = (PFNGLBLENDCOLOREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendColorEXT"); + if (NULL == GLH_EXT_NAME(glBlendColorEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_blend_equation_separate + if (0 == strcmp(extension, "GL_EXT_blend_equation_separate")) { + GLH_EXT_NAME(glBlendEquationSeparateEXT) = (PFNGLBLENDEQUATIONSEPARATEEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendEquationSeparateEXT"); + if (NULL == GLH_EXT_NAME(glBlendEquationSeparateEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_blend_func_separate + if (0 == strcmp(extension, "GL_EXT_blend_func_separate")) { + GLH_EXT_NAME(glBlendFuncSeparateEXT) = (PFNGLBLENDFUNCSEPARATEEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendFuncSeparateEXT"); + if (NULL == GLH_EXT_NAME(glBlendFuncSeparateEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_blend_minmax + if (0 == strcmp(extension, "GL_EXT_blend_minmax")) { + GLH_EXT_NAME(glBlendEquationEXT) = (PFNGLBLENDEQUATIONEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glBlendEquationEXT"); + if (NULL == GLH_EXT_NAME(glBlendEquationEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_blend_subtract + if (0 == strcmp(extension, "GL_EXT_blend_subtract")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_Cg_shader + if (0 == strcmp(extension, "GL_EXT_Cg_shader")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_compiled_vertex_array + if (0 == strcmp(extension, "GL_EXT_compiled_vertex_array")) { + GLH_EXT_NAME(glLockArraysEXT) = (PFNGLLOCKARRAYSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glLockArraysEXT"); + if (NULL == GLH_EXT_NAME(glLockArraysEXT)) + return GL_FALSE; + GLH_EXT_NAME(glUnlockArraysEXT) = (PFNGLUNLOCKARRAYSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glUnlockArraysEXT"); + if (NULL == GLH_EXT_NAME(glUnlockArraysEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_depth_bounds_test + if (0 == strcmp(extension, "GL_EXT_depth_bounds_test")) { + GLH_EXT_NAME(glDepthBoundsEXT) = (PFNGLDEPTHBOUNDSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glDepthBoundsEXT"); + if (NULL == GLH_EXT_NAME(glDepthBoundsEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_draw_range_elements + if (0 == strcmp(extension, "GL_EXT_draw_range_elements")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_fog_coord + if (0 == strcmp(extension, "GL_EXT_fog_coord")) { + GLH_EXT_NAME(glFogCoorddEXT) = (PFNGLFOGCOORDDEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFogCoorddEXT"); + if (NULL == GLH_EXT_NAME(glFogCoorddEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFogCoorddvEXT) = (PFNGLFOGCOORDDVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFogCoorddvEXT"); + if (NULL == GLH_EXT_NAME(glFogCoorddvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFogCoordfEXT) = (PFNGLFOGCOORDFEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFogCoordfEXT"); + if (NULL == GLH_EXT_NAME(glFogCoordfEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFogCoordfvEXT) = (PFNGLFOGCOORDFVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFogCoordfvEXT"); + if (NULL == GLH_EXT_NAME(glFogCoordfvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFogCoordPointerEXT) = (PFNGLFOGCOORDPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFogCoordPointerEXT"); + if (NULL == GLH_EXT_NAME(glFogCoordPointerEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_framebuffer_object + if (0 == strcmp(extension, "GL_EXT_framebuffer_object")) { + GLH_EXT_NAME(glIsRenderbufferEXT) = (PFNGLISRENDERBUFFEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glIsRenderbufferEXT"); + if (NULL == GLH_EXT_NAME(glIsRenderbufferEXT)) + return GL_FALSE; + GLH_EXT_NAME(glBindRenderbufferEXT) = (PFNGLBINDRENDERBUFFEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glBindRenderbufferEXT"); + if (NULL == GLH_EXT_NAME(glBindRenderbufferEXT)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteRenderbuffersEXT) = (PFNGLDELETERENDERBUFFERSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteRenderbuffersEXT"); + if (NULL == GLH_EXT_NAME(glDeleteRenderbuffersEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGenRenderbuffersEXT) = (PFNGLGENRENDERBUFFERSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGenRenderbuffersEXT"); + if (NULL == GLH_EXT_NAME(glGenRenderbuffersEXT)) + return GL_FALSE; + GLH_EXT_NAME(glRenderbufferStorageEXT) = (PFNGLRENDERBUFFERSTORAGEEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glRenderbufferStorageEXT"); + if (NULL == GLH_EXT_NAME(glRenderbufferStorageEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGetRenderbufferParameterivEXT) = (PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGetRenderbufferParameterivEXT"); + if (NULL == GLH_EXT_NAME(glGetRenderbufferParameterivEXT)) + return GL_FALSE; + GLH_EXT_NAME(glIsFramebufferEXT) = (PFNGLISFRAMEBUFFEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glIsFramebufferEXT"); + if (NULL == GLH_EXT_NAME(glIsFramebufferEXT)) + return GL_FALSE; + GLH_EXT_NAME(glBindFramebufferEXT) = (PFNGLBINDFRAMEBUFFEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glBindFramebufferEXT"); + if (NULL == GLH_EXT_NAME(glBindFramebufferEXT)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteFramebuffersEXT) = (PFNGLDELETEFRAMEBUFFERSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteFramebuffersEXT"); + if (NULL == GLH_EXT_NAME(glDeleteFramebuffersEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGenFramebuffersEXT) = (PFNGLGENFRAMEBUFFERSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGenFramebuffersEXT"); + if (NULL == GLH_EXT_NAME(glGenFramebuffersEXT)) + return GL_FALSE; + GLH_EXT_NAME(glCheckFramebufferStatusEXT) = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glCheckFramebufferStatusEXT"); + if (NULL == GLH_EXT_NAME(glCheckFramebufferStatusEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFramebufferTexture1DEXT) = (PFNGLFRAMEBUFFERTEXTURE1DEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFramebufferTexture1DEXT"); + if (NULL == GLH_EXT_NAME(glFramebufferTexture1DEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFramebufferTexture2DEXT) = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFramebufferTexture2DEXT"); + if (NULL == GLH_EXT_NAME(glFramebufferTexture2DEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFramebufferTexture3DEXT) = (PFNGLFRAMEBUFFERTEXTURE3DEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFramebufferTexture3DEXT"); + if (NULL == GLH_EXT_NAME(glFramebufferTexture3DEXT)) + return GL_FALSE; + GLH_EXT_NAME(glFramebufferRenderbufferEXT) = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glFramebufferRenderbufferEXT"); + if (NULL == GLH_EXT_NAME(glFramebufferRenderbufferEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGetFramebufferAttachmentParameterivEXT) = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGetFramebufferAttachmentParameterivEXT"); + if (NULL == GLH_EXT_NAME(glGetFramebufferAttachmentParameterivEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGenerateMipmapEXT) = (PFNGLGENERATEMIPMAPEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGenerateMipmapEXT"); + if (NULL == GLH_EXT_NAME(glGenerateMipmapEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_multi_draw_arrays + if (0 == strcmp(extension, "GL_EXT_multi_draw_arrays")) { + GLH_EXT_NAME(glMultiDrawArraysEXT) = (PFNGLMULTIDRAWARRAYSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawArraysEXT"); + if (NULL == GLH_EXT_NAME(glMultiDrawArraysEXT)) + return GL_FALSE; + GLH_EXT_NAME(glMultiDrawElementsEXT) = (PFNGLMULTIDRAWELEMENTSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawElementsEXT"); + if (NULL == GLH_EXT_NAME(glMultiDrawElementsEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_light_max_exponent + if (0 == strcmp(extension, "GL_EXT_light_max_exponent")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_packed_pixels + if (0 == strcmp(extension, "GL_EXT_packed_pixels")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_paletted_texture + if (0 == strcmp(extension, "GL_EXT_paletted_texture")) { + GLH_EXT_NAME(glColorSubTableEXT) = (PFNGLCOLORSUBTABLEEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glColorSubTableEXT"); + if (NULL == GLH_EXT_NAME(glColorSubTableEXT)) + return GL_FALSE; + GLH_EXT_NAME(glColorTableEXT) = (PFNGLCOLORTABLEEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glColorTableEXT"); + if (NULL == GLH_EXT_NAME(glColorTableEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGetColorTableEXT) = (PFNGLGETCOLORTABLEEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGetColorTableEXT"); + if (NULL == GLH_EXT_NAME(glGetColorTableEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGetColorTableParameterfvEXT) = (PFNGLGETCOLORTABLEPARAMETERFVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGetColorTableParameterfvEXT"); + if (NULL == GLH_EXT_NAME(glGetColorTableParameterfvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGetColorTableParameterivEXT) = (PFNGLGETCOLORTABLEPARAMETERIVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGetColorTableParameterivEXT"); + if (NULL == GLH_EXT_NAME(glGetColorTableParameterivEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_pixel_buffer_object + if (0 == strcmp(extension, "GL_EXT_pixel_buffer_object")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_point_parameters + if (0 == strcmp(extension, "GL_EXT_point_parameters")) { + GLH_EXT_NAME(glPointParameterfEXT) = (PFNGLPOINTPARAMETERFEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterfEXT"); + if (NULL == GLH_EXT_NAME(glPointParameterfEXT)) + return GL_FALSE; + GLH_EXT_NAME(glPointParameterfvEXT) = (PFNGLPOINTPARAMETERFVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterfvEXT"); + if (NULL == GLH_EXT_NAME(glPointParameterfvEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_rescale_normal + if (0 == strcmp(extension, "GL_EXT_rescale_normal")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_secondary_color + if (0 == strcmp(extension, "GL_EXT_secondary_color")) { + GLH_EXT_NAME(glSecondaryColor3bEXT) = (PFNGLSECONDARYCOLOR3BEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3bEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3bEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3bvEXT) = (PFNGLSECONDARYCOLOR3BVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3bvEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3bvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3dEXT) = (PFNGLSECONDARYCOLOR3DEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3dEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3dEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3dvEXT) = (PFNGLSECONDARYCOLOR3DVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3dvEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3dvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3fEXT) = (PFNGLSECONDARYCOLOR3FEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3fEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3fEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3fvEXT) = (PFNGLSECONDARYCOLOR3FVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3fvEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3fvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3iEXT) = (PFNGLSECONDARYCOLOR3IEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3iEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3iEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3ivEXT) = (PFNGLSECONDARYCOLOR3IVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ivEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3ivEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3sEXT) = (PFNGLSECONDARYCOLOR3SEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3sEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3sEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3svEXT) = (PFNGLSECONDARYCOLOR3SVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3svEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3svEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3ubEXT) = (PFNGLSECONDARYCOLOR3UBEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ubEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3ubEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3ubvEXT) = (PFNGLSECONDARYCOLOR3UBVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3ubvEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3ubvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3uiEXT) = (PFNGLSECONDARYCOLOR3UIEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3uiEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3uiEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3uivEXT) = (PFNGLSECONDARYCOLOR3UIVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3uivEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3uivEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3usEXT) = (PFNGLSECONDARYCOLOR3USEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3usEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3usEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3usvEXT) = (PFNGLSECONDARYCOLOR3USVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3usvEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3usvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColorPointerEXT) = (PFNGLSECONDARYCOLORPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColorPointerEXT"); + if (NULL == GLH_EXT_NAME(glSecondaryColorPointerEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_separate_specular_color + if (0 == strcmp(extension, "GL_EXT_separate_specular_color")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_shadow_funcs + if (0 == strcmp(extension, "GL_EXT_shadow_funcs")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_shared_texture_palette + if (0 == strcmp(extension, "GL_EXT_shared_texture_palette")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_stencil_two_side + if (0 == strcmp(extension, "GL_EXT_stencil_two_side")) { + GLH_EXT_NAME(glActiveStencilFaceEXT) = (PFNGLACTIVESTENCILFACEEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glActiveStencilFaceEXT"); + if (NULL == GLH_EXT_NAME(glActiveStencilFaceEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_stencil_wrap + if (0 == strcmp(extension, "GL_EXT_stencil_wrap")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_compression_s3tc + if (0 == strcmp(extension, "GL_EXT_texture_compression_s3tc")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_cube_map + if (0 == strcmp(extension, "GL_EXT_texture_cube_map")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_edge_clamp + if (0 == strcmp(extension, "GL_EXT_texture_edge_clamp")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_env_add + if (0 == strcmp(extension, "GL_EXT_texture_env_add")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_env_combine + if (0 == strcmp(extension, "GL_EXT_texture_env_combine")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_env_dot3 + if (0 == strcmp(extension, "GL_EXT_texture_env_dot3")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_filter_anisotropic + if (0 == strcmp(extension, "GL_EXT_texture_filter_anisotropic")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_lod_bias + if (0 == strcmp(extension, "GL_EXT_texture_lod_bias")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_object + if (0 == strcmp(extension, "GL_EXT_texture_object")) { + GLH_EXT_NAME(glAreTexturesResidentEXT) = (PFNGLARETEXTURESRESIDENTEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glAreTexturesResidentEXT"); + if (NULL == GLH_EXT_NAME(glAreTexturesResidentEXT)) + return GL_FALSE; + GLH_EXT_NAME(glBindTextureEXT) = (PFNGLBINDTEXTUREEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glBindTextureEXT"); + if (NULL == GLH_EXT_NAME(glBindTextureEXT)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteTexturesEXT) = (PFNGLDELETETEXTURESEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteTexturesEXT"); + if (NULL == GLH_EXT_NAME(glDeleteTexturesEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGenTexturesEXT) = (PFNGLGENTEXTURESEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGenTexturesEXT"); + if (NULL == GLH_EXT_NAME(glGenTexturesEXT)) + return GL_FALSE; + GLH_EXT_NAME(glIsTextureEXT) = (PFNGLISTEXTUREEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glIsTextureEXT"); + if (NULL == GLH_EXT_NAME(glIsTextureEXT)) + return GL_FALSE; + GLH_EXT_NAME(glPrioritizeTexturesEXT) = (PFNGLPRIORITIZETEXTURESEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glPrioritizeTexturesEXT"); + if (NULL == GLH_EXT_NAME(glPrioritizeTexturesEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture_rectangle + if (0 == strcmp(extension, "GL_EXT_texture_rectangle")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_texture3D + if (0 == strcmp(extension, "GL_EXT_texture3D")) { + GLH_EXT_NAME(glTexImage3DEXT) = (PFNGLTEXIMAGE3DEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glTexImage3DEXT"); + if (NULL == GLH_EXT_NAME(glTexImage3DEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_vertex_array + if (0 == strcmp(extension, "GL_EXT_vertex_array")) { + GLH_EXT_NAME(glArrayElementEXT) = (PFNGLARRAYELEMENTEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glArrayElementEXT"); + if (NULL == GLH_EXT_NAME(glArrayElementEXT)) + return GL_FALSE; + GLH_EXT_NAME(glColorPointerEXT) = (PFNGLCOLORPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glColorPointerEXT"); + if (NULL == GLH_EXT_NAME(glColorPointerEXT)) + return GL_FALSE; + GLH_EXT_NAME(glEdgeFlagPointerEXT) = (PFNGLEDGEFLAGPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glEdgeFlagPointerEXT"); + if (NULL == GLH_EXT_NAME(glEdgeFlagPointerEXT)) + return GL_FALSE; + GLH_EXT_NAME(glGetPointervEXT) = (PFNGLGETPOINTERVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glGetPointervEXT"); + if (NULL == GLH_EXT_NAME(glGetPointervEXT)) + return GL_FALSE; + GLH_EXT_NAME(glIndexPointerEXT) = (PFNGLINDEXPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glIndexPointerEXT"); + if (NULL == GLH_EXT_NAME(glIndexPointerEXT)) + return GL_FALSE; + GLH_EXT_NAME(glNormalPointerEXT) = (PFNGLNORMALPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glNormalPointerEXT"); + if (NULL == GLH_EXT_NAME(glNormalPointerEXT)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoordPointerEXT) = (PFNGLTEXCOORDPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoordPointerEXT"); + if (NULL == GLH_EXT_NAME(glTexCoordPointerEXT)) + return GL_FALSE; + GLH_EXT_NAME(glVertexPointerEXT) = (PFNGLVERTEXPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexPointerEXT"); + if (NULL == GLH_EXT_NAME(glVertexPointerEXT)) + return GL_FALSE; + GLH_EXT_NAME(glDrawArraysEXT) = (PFNGLDRAWARRAYSEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawArraysEXT"); + if (NULL == GLH_EXT_NAME(glDrawArraysEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_EXT_vertex_weighting + if (0 == strcmp(extension, "GL_EXT_vertex_weighting")) { + GLH_EXT_NAME(glVertexWeightfEXT) = (PFNGLVERTEXWEIGHTFEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexWeightfEXT"); + if (NULL == GLH_EXT_NAME(glVertexWeightfEXT)) + return GL_FALSE; + GLH_EXT_NAME(glVertexWeightfvEXT) = (PFNGLVERTEXWEIGHTFVEXTPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexWeightfvEXT"); + if (NULL == GLH_EXT_NAME(glVertexWeightfvEXT)) + return GL_FALSE; + GLH_EXT_NAME(glVertexWeightPointerEXT) = (PFNGLVERTEXWEIGHTPOINTEREXTPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexWeightPointerEXT"); + if (NULL == GLH_EXT_NAME(glVertexWeightPointerEXT)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_HP_occlusion_test + if (0 == strcmp(extension, "GL_HP_occlusion_test")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_IBM_texture_mirrored_repeat + if (0 == strcmp(extension, "GL_IBM_texture_mirrored_repeat")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_blend_square + if (0 == strcmp(extension, "GL_NV_blend_square")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_copy_depth_to_color + if (0 == strcmp(extension, "GL_NV_copy_depth_to_color")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_depth_clamp + if (0 == strcmp(extension, "GL_NV_depth_clamp")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_element_array + if (0 == strcmp(extension, "GL_NV_element_array")) { + GLH_EXT_NAME(glElementPointerNV) = (PFNGLELEMENTPOINTERNVPROC)GLH_EXT_GET_PROC_ADDRESS("glElementPointerNV"); + if (NULL == GLH_EXT_NAME(glElementPointerNV)) + return GL_FALSE; + GLH_EXT_NAME(glDrawElementArrayNV) = (PFNGLDRAWELEMENTARRAYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawElementArrayNV"); + if (NULL == GLH_EXT_NAME(glDrawElementArrayNV)) + return GL_FALSE; + GLH_EXT_NAME(glDrawRangeElementArrayNV) = (PFNGLDRAWRANGEELEMENTARRAYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawRangeElementArrayNV"); + if (NULL == GLH_EXT_NAME(glDrawRangeElementArrayNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiDrawElementArrayNV) = (PFNGLMULTIDRAWELEMENTARRAYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawElementArrayNV"); + if (NULL == GLH_EXT_NAME(glMultiDrawElementArrayNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiDrawRangeElementArrayNV) = (PFNGLMULTIDRAWRANGEELEMENTARRAYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiDrawRangeElementArrayNV"); + if (NULL == GLH_EXT_NAME(glMultiDrawRangeElementArrayNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_fence + if (0 == strcmp(extension, "GL_NV_fence")) { + GLH_EXT_NAME(glGenFencesNV) = (PFNGLGENFENCESNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGenFencesNV"); + if (NULL == GLH_EXT_NAME(glGenFencesNV)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteFencesNV) = (PFNGLDELETEFENCESNVPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteFencesNV"); + if (NULL == GLH_EXT_NAME(glDeleteFencesNV)) + return GL_FALSE; + GLH_EXT_NAME(glSetFenceNV) = (PFNGLSETFENCENVPROC)GLH_EXT_GET_PROC_ADDRESS("glSetFenceNV"); + if (NULL == GLH_EXT_NAME(glSetFenceNV)) + return GL_FALSE; + GLH_EXT_NAME(glTestFenceNV) = (PFNGLTESTFENCENVPROC)GLH_EXT_GET_PROC_ADDRESS("glTestFenceNV"); + if (NULL == GLH_EXT_NAME(glTestFenceNV)) + return GL_FALSE; + GLH_EXT_NAME(glFinishFenceNV) = (PFNGLFINISHFENCENVPROC)GLH_EXT_GET_PROC_ADDRESS("glFinishFenceNV"); + if (NULL == GLH_EXT_NAME(glFinishFenceNV)) + return GL_FALSE; + GLH_EXT_NAME(glIsFenceNV) = (PFNGLISFENCENVPROC)GLH_EXT_GET_PROC_ADDRESS("glIsFenceNV"); + if (NULL == GLH_EXT_NAME(glIsFenceNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetFenceivNV) = (PFNGLGETFENCEIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetFenceivNV"); + if (NULL == GLH_EXT_NAME(glGetFenceivNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_float_buffer + if (0 == strcmp(extension, "GL_NV_float_buffer")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_fog_distance + if (0 == strcmp(extension, "GL_NV_fog_distance")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_fragment_program + if (0 == strcmp(extension, "GL_NV_fragment_program")) { + GLH_EXT_NAME(glProgramNamedParameter4fNV) = (PFNGLPROGRAMNAMEDPARAMETER4FNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramNamedParameter4fNV"); + if (NULL == GLH_EXT_NAME(glProgramNamedParameter4fNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramNamedParameter4dNV) = (PFNGLPROGRAMNAMEDPARAMETER4DNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramNamedParameter4dNV"); + if (NULL == GLH_EXT_NAME(glProgramNamedParameter4dNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramNamedParameter4fvNV) = (PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramNamedParameter4fvNV"); + if (NULL == GLH_EXT_NAME(glProgramNamedParameter4fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramNamedParameter4dvNV) = (PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramNamedParameter4dvNV"); + if (NULL == GLH_EXT_NAME(glProgramNamedParameter4dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramNamedParameterfvNV) = (PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramNamedParameterfvNV"); + if (NULL == GLH_EXT_NAME(glGetProgramNamedParameterfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramNamedParameterdvNV) = (PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramNamedParameterdvNV"); + if (NULL == GLH_EXT_NAME(glGetProgramNamedParameterdvNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_fragment_program2 + if (0 == strcmp(extension, "GL_NV_fragment_program2")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_half_float + if (0 == strcmp(extension, "GL_NV_half_float")) { + GLH_EXT_NAME(glVertex2hNV) = (PFNGLVERTEX2HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertex2hNV"); + if (NULL == GLH_EXT_NAME(glVertex2hNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertex2hvNV) = (PFNGLVERTEX2HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertex2hvNV"); + if (NULL == GLH_EXT_NAME(glVertex2hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertex3hNV) = (PFNGLVERTEX3HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertex3hNV"); + if (NULL == GLH_EXT_NAME(glVertex3hNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertex3hvNV) = (PFNGLVERTEX3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertex3hvNV"); + if (NULL == GLH_EXT_NAME(glVertex3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertex4hNV) = (PFNGLVERTEX4HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertex4hNV"); + if (NULL == GLH_EXT_NAME(glVertex4hNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertex4hvNV) = (PFNGLVERTEX4HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertex4hvNV"); + if (NULL == GLH_EXT_NAME(glVertex4hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glNormal3hNV) = (PFNGLNORMAL3HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glNormal3hNV"); + if (NULL == GLH_EXT_NAME(glNormal3hNV)) + return GL_FALSE; + GLH_EXT_NAME(glNormal3hvNV) = (PFNGLNORMAL3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glNormal3hvNV"); + if (NULL == GLH_EXT_NAME(glNormal3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glColor3hNV) = (PFNGLCOLOR3HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glColor3hNV"); + if (NULL == GLH_EXT_NAME(glColor3hNV)) + return GL_FALSE; + GLH_EXT_NAME(glColor3hvNV) = (PFNGLCOLOR3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glColor3hvNV"); + if (NULL == GLH_EXT_NAME(glColor3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glColor4hNV) = (PFNGLCOLOR4HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glColor4hNV"); + if (NULL == GLH_EXT_NAME(glColor4hNV)) + return GL_FALSE; + GLH_EXT_NAME(glColor4hvNV) = (PFNGLCOLOR4HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glColor4hvNV"); + if (NULL == GLH_EXT_NAME(glColor4hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord1hNV) = (PFNGLTEXCOORD1HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord1hNV"); + if (NULL == GLH_EXT_NAME(glTexCoord1hNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord1hvNV) = (PFNGLTEXCOORD1HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord1hvNV"); + if (NULL == GLH_EXT_NAME(glTexCoord1hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord2hNV) = (PFNGLTEXCOORD2HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord2hNV"); + if (NULL == GLH_EXT_NAME(glTexCoord2hNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord2hvNV) = (PFNGLTEXCOORD2HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord2hvNV"); + if (NULL == GLH_EXT_NAME(glTexCoord2hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord3hNV) = (PFNGLTEXCOORD3HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord3hNV"); + if (NULL == GLH_EXT_NAME(glTexCoord3hNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord3hvNV) = (PFNGLTEXCOORD3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord3hvNV"); + if (NULL == GLH_EXT_NAME(glTexCoord3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord4hNV) = (PFNGLTEXCOORD4HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord4hNV"); + if (NULL == GLH_EXT_NAME(glTexCoord4hNV)) + return GL_FALSE; + GLH_EXT_NAME(glTexCoord4hvNV) = (PFNGLTEXCOORD4HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTexCoord4hvNV"); + if (NULL == GLH_EXT_NAME(glTexCoord4hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1hNV) = (PFNGLMULTITEXCOORD1HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1hNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1hNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord1hvNV) = (PFNGLMULTITEXCOORD1HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord1hvNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord1hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2hNV) = (PFNGLMULTITEXCOORD2HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2hNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2hNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord2hvNV) = (PFNGLMULTITEXCOORD2HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord2hvNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord2hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3hNV) = (PFNGLMULTITEXCOORD3HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3hNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3hNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord3hvNV) = (PFNGLMULTITEXCOORD3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord3hvNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4hNV) = (PFNGLMULTITEXCOORD4HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4hNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4hNV)) + return GL_FALSE; + GLH_EXT_NAME(glMultiTexCoord4hvNV) = (PFNGLMULTITEXCOORD4HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glMultiTexCoord4hvNV"); + if (NULL == GLH_EXT_NAME(glMultiTexCoord4hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glFogCoordhNV) = (PFNGLFOGCOORDHNVPROC)GLH_EXT_GET_PROC_ADDRESS("glFogCoordhNV"); + if (NULL == GLH_EXT_NAME(glFogCoordhNV)) + return GL_FALSE; + GLH_EXT_NAME(glFogCoordhvNV) = (PFNGLFOGCOORDHVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glFogCoordhvNV"); + if (NULL == GLH_EXT_NAME(glFogCoordhvNV)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3hNV) = (PFNGLSECONDARYCOLOR3HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3hNV"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3hNV)) + return GL_FALSE; + GLH_EXT_NAME(glSecondaryColor3hvNV) = (PFNGLSECONDARYCOLOR3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glSecondaryColor3hvNV"); + if (NULL == GLH_EXT_NAME(glSecondaryColor3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1hNV) = (PFNGLVERTEXATTRIB1HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1hNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1hNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1hvNV) = (PFNGLVERTEXATTRIB1HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2hNV) = (PFNGLVERTEXATTRIB2HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2hNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2hNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2hvNV) = (PFNGLVERTEXATTRIB2HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3hNV) = (PFNGLVERTEXATTRIB3HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3hNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3hNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3hvNV) = (PFNGLVERTEXATTRIB3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4hNV) = (PFNGLVERTEXATTRIB4HNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4hNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4hNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4hvNV) = (PFNGLVERTEXATTRIB4HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs1hvNV) = (PFNGLVERTEXATTRIBS1HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs1hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs1hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs2hvNV) = (PFNGLVERTEXATTRIBS2HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs2hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs2hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs3hvNV) = (PFNGLVERTEXATTRIBS3HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs3hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs3hvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs4hvNV) = (PFNGLVERTEXATTRIBS4HVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs4hvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs4hvNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_light_max_exponent + if (0 == strcmp(extension, "GL_NV_light_max_exponent")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_multisample_filter_hint + if (0 == strcmp(extension, "GL_NV_multisample_filter_hint")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_occlusion_query + if (0 == strcmp(extension, "GL_NV_occlusion_query")) { + GLH_EXT_NAME(glGenOcclusionQueriesNV) = (PFNGLGENOCCLUSIONQUERIESNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGenOcclusionQueriesNV"); + if (NULL == GLH_EXT_NAME(glGenOcclusionQueriesNV)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteOcclusionQueriesNV) = (PFNGLDELETEOCCLUSIONQUERIESNVPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteOcclusionQueriesNV"); + if (NULL == GLH_EXT_NAME(glDeleteOcclusionQueriesNV)) + return GL_FALSE; + GLH_EXT_NAME(glIsOcclusionQueryNV) = (PFNGLISOCCLUSIONQUERYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glIsOcclusionQueryNV"); + if (NULL == GLH_EXT_NAME(glIsOcclusionQueryNV)) + return GL_FALSE; + GLH_EXT_NAME(glBeginOcclusionQueryNV) = (PFNGLBEGINOCCLUSIONQUERYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glBeginOcclusionQueryNV"); + if (NULL == GLH_EXT_NAME(glBeginOcclusionQueryNV)) + return GL_FALSE; + GLH_EXT_NAME(glEndOcclusionQueryNV) = (PFNGLENDOCCLUSIONQUERYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glEndOcclusionQueryNV"); + if (NULL == GLH_EXT_NAME(glEndOcclusionQueryNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetOcclusionQueryivNV) = (PFNGLGETOCCLUSIONQUERYIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetOcclusionQueryivNV"); + if (NULL == GLH_EXT_NAME(glGetOcclusionQueryivNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetOcclusionQueryuivNV) = (PFNGLGETOCCLUSIONQUERYUIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetOcclusionQueryuivNV"); + if (NULL == GLH_EXT_NAME(glGetOcclusionQueryuivNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_packed_depth_stencil + if (0 == strcmp(extension, "GL_NV_packed_depth_stencil")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_pixel_buffer_object + if (0 == strcmp(extension, "GL_NV_pixel_buffer_object")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_pixel_data_range + if (0 == strcmp(extension, "GL_NV_pixel_data_range")) { + GLH_EXT_NAME(glPixelDataRangeNV) = (PFNGLPIXELDATARANGENVPROC)GLH_EXT_GET_PROC_ADDRESS("glPixelDataRangeNV"); + if (NULL == GLH_EXT_NAME(glPixelDataRangeNV)) + return GL_FALSE; + GLH_EXT_NAME(glFlushPixelDataRangeNV) = (PFNGLFLUSHPIXELDATARANGENVPROC)GLH_EXT_GET_PROC_ADDRESS("glFlushPixelDataRangeNV"); + if (NULL == GLH_EXT_NAME(glFlushPixelDataRangeNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_point_sprite + if (0 == strcmp(extension, "GL_NV_point_sprite")) { + GLH_EXT_NAME(glPointParameteriNV) = (PFNGLPOINTPARAMETERINVPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameteriNV"); + if (NULL == GLH_EXT_NAME(glPointParameteriNV)) + return GL_FALSE; + GLH_EXT_NAME(glPointParameterivNV) = (PFNGLPOINTPARAMETERIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glPointParameterivNV"); + if (NULL == GLH_EXT_NAME(glPointParameterivNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_primitive_restart + if (0 == strcmp(extension, "GL_NV_primitive_restart")) { + GLH_EXT_NAME(glPrimitiveRestartNV) = (PFNGLPRIMITIVERESTARTNVPROC)GLH_EXT_GET_PROC_ADDRESS("glPrimitiveRestartNV"); + if (NULL == GLH_EXT_NAME(glPrimitiveRestartNV)) + return GL_FALSE; + GLH_EXT_NAME(glPrimitiveRestartIndexNV) = (PFNGLPRIMITIVERESTARTINDEXNVPROC)GLH_EXT_GET_PROC_ADDRESS("glPrimitiveRestartIndexNV"); + if (NULL == GLH_EXT_NAME(glPrimitiveRestartIndexNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_register_combiners + if (0 == strcmp(extension, "GL_NV_register_combiners")) { + GLH_EXT_NAME(glCombinerParameterfvNV) = (PFNGLCOMBINERPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glCombinerParameterfvNV"); + if (NULL == GLH_EXT_NAME(glCombinerParameterfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glCombinerParameterfNV) = (PFNGLCOMBINERPARAMETERFNVPROC)GLH_EXT_GET_PROC_ADDRESS("glCombinerParameterfNV"); + if (NULL == GLH_EXT_NAME(glCombinerParameterfNV)) + return GL_FALSE; + GLH_EXT_NAME(glCombinerParameterivNV) = (PFNGLCOMBINERPARAMETERIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glCombinerParameterivNV"); + if (NULL == GLH_EXT_NAME(glCombinerParameterivNV)) + return GL_FALSE; + GLH_EXT_NAME(glCombinerParameteriNV) = (PFNGLCOMBINERPARAMETERINVPROC)GLH_EXT_GET_PROC_ADDRESS("glCombinerParameteriNV"); + if (NULL == GLH_EXT_NAME(glCombinerParameteriNV)) + return GL_FALSE; + GLH_EXT_NAME(glCombinerInputNV) = (PFNGLCOMBINERINPUTNVPROC)GLH_EXT_GET_PROC_ADDRESS("glCombinerInputNV"); + if (NULL == GLH_EXT_NAME(glCombinerInputNV)) + return GL_FALSE; + GLH_EXT_NAME(glCombinerOutputNV) = (PFNGLCOMBINEROUTPUTNVPROC)GLH_EXT_GET_PROC_ADDRESS("glCombinerOutputNV"); + if (NULL == GLH_EXT_NAME(glCombinerOutputNV)) + return GL_FALSE; + GLH_EXT_NAME(glFinalCombinerInputNV) = (PFNGLFINALCOMBINERINPUTNVPROC)GLH_EXT_GET_PROC_ADDRESS("glFinalCombinerInputNV"); + if (NULL == GLH_EXT_NAME(glFinalCombinerInputNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetCombinerInputParameterfvNV) = (PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCombinerInputParameterfvNV"); + if (NULL == GLH_EXT_NAME(glGetCombinerInputParameterfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetCombinerInputParameterivNV) = (PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCombinerInputParameterivNV"); + if (NULL == GLH_EXT_NAME(glGetCombinerInputParameterivNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetCombinerOutputParameterfvNV) = (PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCombinerOutputParameterfvNV"); + if (NULL == GLH_EXT_NAME(glGetCombinerOutputParameterfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetCombinerOutputParameterivNV) = (PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCombinerOutputParameterivNV"); + if (NULL == GLH_EXT_NAME(glGetCombinerOutputParameterivNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetFinalCombinerInputParameterfvNV) = (PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetFinalCombinerInputParameterfvNV"); + if (NULL == GLH_EXT_NAME(glGetFinalCombinerInputParameterfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetFinalCombinerInputParameterivNV) = (PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetFinalCombinerInputParameterivNV"); + if (NULL == GLH_EXT_NAME(glGetFinalCombinerInputParameterivNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_register_combiners2 + if (0 == strcmp(extension, "GL_NV_register_combiners2")) { + GLH_EXT_NAME(glCombinerStageParameterfvNV) = (PFNGLCOMBINERSTAGEPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glCombinerStageParameterfvNV"); + if (NULL == GLH_EXT_NAME(glCombinerStageParameterfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetCombinerStageParameterfvNV) = (PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetCombinerStageParameterfvNV"); + if (NULL == GLH_EXT_NAME(glGetCombinerStageParameterfvNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_stencil_two_side + if (0 == strcmp(extension, "GL_NV_stencil_two_side")) { + GLH_EXT_NAME(glActiveStencilFaceNV) = (PFNGLACTIVESTENCILFACENVPROC)GLH_EXT_GET_PROC_ADDRESS("glActiveStencilFaceNV"); + if (NULL == GLH_EXT_NAME(glActiveStencilFaceNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texgen_reflection + if (0 == strcmp(extension, "GL_NV_texgen_reflection")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texture_compression_vtc + if (0 == strcmp(extension, "GL_NV_texture_compression_vtc")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texture_env_combine4 + if (0 == strcmp(extension, "GL_NV_texture_env_combine4")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texture_expand_normal + if (0 == strcmp(extension, "GL_NV_texture_expand_normal")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texture_rectangle + if (0 == strcmp(extension, "GL_NV_texture_rectangle")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texture_shader + if (0 == strcmp(extension, "GL_NV_texture_shader")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texture_shader2 + if (0 == strcmp(extension, "GL_NV_texture_shader2")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_texture_shader3 + if (0 == strcmp(extension, "GL_NV_texture_shader3")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_vertex_array_range + if (0 == strcmp(extension, "GL_NV_vertex_array_range")) { + GLH_EXT_NAME(glFlushVertexArrayRangeNV) = (PFNGLFLUSHVERTEXARRAYRANGENVPROC)GLH_EXT_GET_PROC_ADDRESS("glFlushVertexArrayRangeNV"); + if (NULL == GLH_EXT_NAME(glFlushVertexArrayRangeNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexArrayRangeNV) = (PFNGLVERTEXARRAYRANGENVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexArrayRangeNV"); + if (NULL == GLH_EXT_NAME(glVertexArrayRangeNV)) + return GL_FALSE; +# ifdef _WIN32 + GLH_EXT_NAME(wglAllocateMemoryNV) = (PFNWGLALLOCATEMEMORYNVPROC)GLH_EXT_GET_PROC_ADDRESS("wglAllocateMemoryNV"); + if (NULL == GLH_EXT_NAME(wglAllocateMemoryNV)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXAllocateMemoryNV) = (PFNGLXALLOCATEMEMORYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glXAllocateMemoryNV"); + if (NULL == GLH_EXT_NAME(glXAllocateMemoryNV)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglFreeMemoryNV) = (PFNWGLFREEMEMORYNVPROC)GLH_EXT_GET_PROC_ADDRESS("wglFreeMemoryNV"); + if (NULL == GLH_EXT_NAME(wglFreeMemoryNV)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXFreeMemoryNV) = (PFNGLXFREEMEMORYNVPROC)GLH_EXT_GET_PROC_ADDRESS("glXFreeMemoryNV"); + if (NULL == GLH_EXT_NAME(glXFreeMemoryNV)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_vertex_array_range2 + if (0 == strcmp(extension, "GL_NV_vertex_array_range2")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_vertex_program + if (0 == strcmp(extension, "GL_NV_vertex_program")) { + GLH_EXT_NAME(glAreProgramsResidentNV) = (PFNGLAREPROGRAMSRESIDENTNVPROC)GLH_EXT_GET_PROC_ADDRESS("glAreProgramsResidentNV"); + if (NULL == GLH_EXT_NAME(glAreProgramsResidentNV)) + return GL_FALSE; + GLH_EXT_NAME(glBindProgramNV) = (PFNGLBINDPROGRAMNVPROC)GLH_EXT_GET_PROC_ADDRESS("glBindProgramNV"); + if (NULL == GLH_EXT_NAME(glBindProgramNV)) + return GL_FALSE; + GLH_EXT_NAME(glDeleteProgramsNV) = (PFNGLDELETEPROGRAMSNVPROC)GLH_EXT_GET_PROC_ADDRESS("glDeleteProgramsNV"); + if (NULL == GLH_EXT_NAME(glDeleteProgramsNV)) + return GL_FALSE; + GLH_EXT_NAME(glExecuteProgramNV) = (PFNGLEXECUTEPROGRAMNVPROC)GLH_EXT_GET_PROC_ADDRESS("glExecuteProgramNV"); + if (NULL == GLH_EXT_NAME(glExecuteProgramNV)) + return GL_FALSE; + GLH_EXT_NAME(glGenProgramsNV) = (PFNGLGENPROGRAMSNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGenProgramsNV"); + if (NULL == GLH_EXT_NAME(glGenProgramsNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramParameterdvNV) = (PFNGLGETPROGRAMPARAMETERDVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramParameterdvNV"); + if (NULL == GLH_EXT_NAME(glGetProgramParameterdvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramParameterfvNV) = (PFNGLGETPROGRAMPARAMETERFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramParameterfvNV"); + if (NULL == GLH_EXT_NAME(glGetProgramParameterfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramivNV) = (PFNGLGETPROGRAMIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramivNV"); + if (NULL == GLH_EXT_NAME(glGetProgramivNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetProgramStringNV) = (PFNGLGETPROGRAMSTRINGNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetProgramStringNV"); + if (NULL == GLH_EXT_NAME(glGetProgramStringNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetTrackMatrixivNV) = (PFNGLGETTRACKMATRIXIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetTrackMatrixivNV"); + if (NULL == GLH_EXT_NAME(glGetTrackMatrixivNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribdvNV) = (PFNGLGETVERTEXATTRIBDVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribdvNV"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribdvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribfvNV) = (PFNGLGETVERTEXATTRIBFVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribfvNV"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribfvNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribivNV) = (PFNGLGETVERTEXATTRIBIVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribivNV"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribivNV)) + return GL_FALSE; + GLH_EXT_NAME(glGetVertexAttribPointervNV) = (PFNGLGETVERTEXATTRIBPOINTERVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glGetVertexAttribPointervNV"); + if (NULL == GLH_EXT_NAME(glGetVertexAttribPointervNV)) + return GL_FALSE; + GLH_EXT_NAME(glIsProgramNV) = (PFNGLISPROGRAMNVPROC)GLH_EXT_GET_PROC_ADDRESS("glIsProgramNV"); + if (NULL == GLH_EXT_NAME(glIsProgramNV)) + return GL_FALSE; + GLH_EXT_NAME(glLoadProgramNV) = (PFNGLLOADPROGRAMNVPROC)GLH_EXT_GET_PROC_ADDRESS("glLoadProgramNV"); + if (NULL == GLH_EXT_NAME(glLoadProgramNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramParameter4dNV) = (PFNGLPROGRAMPARAMETER4DNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramParameter4dNV"); + if (NULL == GLH_EXT_NAME(glProgramParameter4dNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramParameter4dvNV) = (PFNGLPROGRAMPARAMETER4DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramParameter4dvNV"); + if (NULL == GLH_EXT_NAME(glProgramParameter4dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramParameter4fNV) = (PFNGLPROGRAMPARAMETER4FNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramParameter4fNV"); + if (NULL == GLH_EXT_NAME(glProgramParameter4fNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramParameter4fvNV) = (PFNGLPROGRAMPARAMETER4FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramParameter4fvNV"); + if (NULL == GLH_EXT_NAME(glProgramParameter4fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramParameters4dvNV) = (PFNGLPROGRAMPARAMETERS4DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramParameters4dvNV"); + if (NULL == GLH_EXT_NAME(glProgramParameters4dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glProgramParameters4fvNV) = (PFNGLPROGRAMPARAMETERS4FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glProgramParameters4fvNV"); + if (NULL == GLH_EXT_NAME(glProgramParameters4fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glRequestResidentProgramsNV) = (PFNGLREQUESTRESIDENTPROGRAMSNVPROC)GLH_EXT_GET_PROC_ADDRESS("glRequestResidentProgramsNV"); + if (NULL == GLH_EXT_NAME(glRequestResidentProgramsNV)) + return GL_FALSE; + GLH_EXT_NAME(glTrackMatrixNV) = (PFNGLTRACKMATRIXNVPROC)GLH_EXT_GET_PROC_ADDRESS("glTrackMatrixNV"); + if (NULL == GLH_EXT_NAME(glTrackMatrixNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribPointerNV) = (PFNGLVERTEXATTRIBPOINTERNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribPointerNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribPointerNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1dNV) = (PFNGLVERTEXATTRIB1DNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1dNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1dNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1dvNV) = (PFNGLVERTEXATTRIB1DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1fNV) = (PFNGLVERTEXATTRIB1FNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1fNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1fNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1fvNV) = (PFNGLVERTEXATTRIB1FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1sNV) = (PFNGLVERTEXATTRIB1SNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1sNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1sNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib1svNV) = (PFNGLVERTEXATTRIB1SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib1svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib1svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2dNV) = (PFNGLVERTEXATTRIB2DNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2dNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2dNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2dvNV) = (PFNGLVERTEXATTRIB2DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2fNV) = (PFNGLVERTEXATTRIB2FNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2fNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2fNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2fvNV) = (PFNGLVERTEXATTRIB2FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2sNV) = (PFNGLVERTEXATTRIB2SNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2sNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2sNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib2svNV) = (PFNGLVERTEXATTRIB2SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib2svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib2svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3dNV) = (PFNGLVERTEXATTRIB3DNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3dNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3dNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3dvNV) = (PFNGLVERTEXATTRIB3DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3fNV) = (PFNGLVERTEXATTRIB3FNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3fNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3fNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3fvNV) = (PFNGLVERTEXATTRIB3FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3sNV) = (PFNGLVERTEXATTRIB3SNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3sNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3sNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib3svNV) = (PFNGLVERTEXATTRIB3SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib3svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib3svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4dNV) = (PFNGLVERTEXATTRIB4DNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4dNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4dNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4dvNV) = (PFNGLVERTEXATTRIB4DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4fNV) = (PFNGLVERTEXATTRIB4FNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4fNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4fNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4fvNV) = (PFNGLVERTEXATTRIB4FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4sNV) = (PFNGLVERTEXATTRIB4SNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4sNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4sNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4svNV) = (PFNGLVERTEXATTRIB4SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttrib4ubvNV) = (PFNGLVERTEXATTRIB4UBVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttrib4ubvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttrib4ubvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs1dvNV) = (PFNGLVERTEXATTRIBS1DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs1dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs1dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs1fvNV) = (PFNGLVERTEXATTRIBS1FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs1fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs1fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs1svNV) = (PFNGLVERTEXATTRIBS1SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs1svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs1svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs2dvNV) = (PFNGLVERTEXATTRIBS2DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs2dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs2dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs2fvNV) = (PFNGLVERTEXATTRIBS2FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs2fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs2fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs2svNV) = (PFNGLVERTEXATTRIBS2SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs2svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs2svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs3dvNV) = (PFNGLVERTEXATTRIBS3DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs3dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs3dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs3fvNV) = (PFNGLVERTEXATTRIBS3FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs3fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs3fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs3svNV) = (PFNGLVERTEXATTRIBS3SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs3svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs3svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs4dvNV) = (PFNGLVERTEXATTRIBS4DVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs4dvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs4dvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs4fvNV) = (PFNGLVERTEXATTRIBS4FVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs4fvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs4fvNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs4svNV) = (PFNGLVERTEXATTRIBS4SVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs4svNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs4svNV)) + return GL_FALSE; + GLH_EXT_NAME(glVertexAttribs4ubvNV) = (PFNGLVERTEXATTRIBS4UBVNVPROC)GLH_EXT_GET_PROC_ADDRESS("glVertexAttribs4ubvNV"); + if (NULL == GLH_EXT_NAME(glVertexAttribs4ubvNV)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_vertex_program1_1 + if (0 == strcmp(extension, "GL_NV_vertex_program1_1")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_vertex_program2 + if (0 == strcmp(extension, "GL_NV_vertex_program2")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_vertex_program2_option + if (0 == strcmp(extension, "GL_NV_vertex_program2_option")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NV_vertex_program3 + if (0 == strcmp(extension, "GL_NV_vertex_program3")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_SGIS_generate_mipmap + if (0 == strcmp(extension, "GL_SGIS_generate_mipmap")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_SGIS_texture_lod + if (0 == strcmp(extension, "GL_SGIS_texture_lod")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_SGIX_depth_texture + if (0 == strcmp(extension, "GL_SGIX_depth_texture")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_SGIX_shadow + if (0 == strcmp(extension, "GL_SGIX_shadow")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_WIN_swap_hint + if (0 == strcmp(extension, "GL_WIN_swap_hint")) { + GLH_EXT_NAME(glAddSwapHintRectWIN) = (PFNGLADDSWAPHINTRECTWINPROC)GLH_EXT_GET_PROC_ADDRESS("glAddSwapHintRectWIN"); + if (NULL == GLH_EXT_NAME(glAddSwapHintRectWIN)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef WGL_ARB_buffer_region + if (0 == strcmp(extension, "WGL_ARB_buffer_region")) { +# ifdef _WIN32 + GLH_EXT_NAME(wglCreateBufferRegionARB) = (PFNWGLCREATEBUFFERREGIONARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglCreateBufferRegionARB"); + if (NULL == GLH_EXT_NAME(wglCreateBufferRegionARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglDeleteBufferRegionARB) = (PFNWGLDELETEBUFFERREGIONARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglDeleteBufferRegionARB"); + if (NULL == GLH_EXT_NAME(wglDeleteBufferRegionARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglSaveBufferRegionARB) = (PFNWGLSAVEBUFFERREGIONARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglSaveBufferRegionARB"); + if (NULL == GLH_EXT_NAME(wglSaveBufferRegionARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglRestoreBufferRegionARB) = (PFNWGLRESTOREBUFFERREGIONARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglRestoreBufferRegionARB"); + if (NULL == GLH_EXT_NAME(wglRestoreBufferRegionARB)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef WGL_ARB_extensions_string + if (0 == strcmp(extension, "WGL_ARB_extensions_string")) { +# ifdef _WIN32 + GLH_EXT_NAME(wglGetExtensionsStringARB) = (PFNWGLGETEXTENSIONSSTRINGARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetExtensionsStringARB"); + if (NULL == GLH_EXT_NAME(wglGetExtensionsStringARB)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef WGL_ARB_pbuffer + if (0 == strcmp(extension, "WGL_ARB_pbuffer")) { +# ifdef _WIN32 + GLH_EXT_NAME(wglCreatePbufferARB) = (PFNWGLCREATEPBUFFERARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglCreatePbufferARB"); + if (NULL == GLH_EXT_NAME(wglCreatePbufferARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglGetPbufferDCARB) = (PFNWGLGETPBUFFERDCARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetPbufferDCARB"); + if (NULL == GLH_EXT_NAME(wglGetPbufferDCARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglReleasePbufferDCARB) = (PFNWGLRELEASEPBUFFERDCARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglReleasePbufferDCARB"); + if (NULL == GLH_EXT_NAME(wglReleasePbufferDCARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglDestroyPbufferARB) = (PFNWGLDESTROYPBUFFERARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglDestroyPbufferARB"); + if (NULL == GLH_EXT_NAME(wglDestroyPbufferARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglQueryPbufferARB) = (PFNWGLQUERYPBUFFERARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglQueryPbufferARB"); + if (NULL == GLH_EXT_NAME(wglQueryPbufferARB)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef WGL_ARB_pixel_format + if (0 == strcmp(extension, "WGL_ARB_pixel_format")) { +# ifdef _WIN32 + GLH_EXT_NAME(wglGetPixelFormatAttribivARB) = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetPixelFormatAttribivARB"); + if (NULL == GLH_EXT_NAME(wglGetPixelFormatAttribivARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglGetPixelFormatAttribfvARB) = (PFNWGLGETPIXELFORMATATTRIBFVARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetPixelFormatAttribfvARB"); + if (NULL == GLH_EXT_NAME(wglGetPixelFormatAttribfvARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglChoosePixelFormatARB) = (PFNWGLCHOOSEPIXELFORMATARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglChoosePixelFormatARB"); + if (NULL == GLH_EXT_NAME(wglChoosePixelFormatARB)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef WGL_ARB_render_texture + if (0 == strcmp(extension, "WGL_ARB_render_texture")) { +# ifdef _WIN32 + GLH_EXT_NAME(wglBindTexImageARB) = (PFNWGLBINDTEXIMAGEARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglBindTexImageARB"); + if (NULL == GLH_EXT_NAME(wglBindTexImageARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglReleaseTexImageARB) = (PFNWGLRELEASETEXIMAGEARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglReleaseTexImageARB"); + if (NULL == GLH_EXT_NAME(wglReleaseTexImageARB)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglSetPbufferAttribARB) = (PFNWGLSETPBUFFERATTRIBARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglSetPbufferAttribARB"); + if (NULL == GLH_EXT_NAME(wglSetPbufferAttribARB)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef WGL_ATI_pixel_format_float + if (0 == strcmp(extension, "WGL_ATI_pixel_format_float")) { + + return GL_TRUE; + } +#endif + +#ifdef WGL_EXT_extensions_string + if (0 == strcmp(extension, "WGL_EXT_extensions_string")) { +# ifdef _WIN32 + GLH_EXT_NAME(wglGetExtensionsStringEXT) = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetExtensionsStringEXT"); + if (NULL == GLH_EXT_NAME(wglGetExtensionsStringEXT)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef WGL_EXT_swap_control + if (0 == strcmp(extension, "WGL_EXT_swap_control")) { +# ifdef _WIN32 + GLH_EXT_NAME(wglSwapIntervalEXT) = (PFNWGLSWAPINTERVALEXTPROC)GLH_EXT_GET_PROC_ADDRESS("wglSwapIntervalEXT"); + if (NULL == GLH_EXT_NAME(wglSwapIntervalEXT)) + return GL_FALSE; +# endif +# ifdef _WIN32 + GLH_EXT_NAME(wglGetSwapIntervalEXT) = (PFNWGLGETSWAPINTERVALEXTPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetSwapIntervalEXT"); + if (NULL == GLH_EXT_NAME(wglGetSwapIntervalEXT)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef WGL_NV_float_buffer + if (0 == strcmp(extension, "WGL_NV_float_buffer")) { + + return GL_TRUE; + } +#endif + +#ifdef WGL_NV_render_depth_texture + if (0 == strcmp(extension, "WGL_NV_render_depth_texture")) { + + return GL_TRUE; + } +#endif + +#ifdef WGL_NV_render_texture_rectangle + if (0 == strcmp(extension, "WGL_NV_render_texture_rectangle")) { + + return GL_TRUE; + } +#endif + +#ifdef GLX_NV_float_buffer + if (0 == strcmp(extension, "GLX_NV_float_buffer")) { + + return GL_TRUE; + } +#endif + +#ifdef GL_NVX_conditional_render + if (0 == strcmp(extension, "GL_NVX_conditional_render")) { + GLH_EXT_NAME(glBeginConditionalRenderNVX) = (PFNGLBEGINCONDITIONALRENDERNVXPROC)GLH_EXT_GET_PROC_ADDRESS("glBeginConditionalRenderNVX"); + if (NULL == GLH_EXT_NAME(glBeginConditionalRenderNVX)) + return GL_FALSE; + GLH_EXT_NAME(glEndConditionalRenderNVX) = (PFNGLENDCONDITIONALRENDERNVXPROC)GLH_EXT_GET_PROC_ADDRESS("glEndConditionalRenderNVX"); + if (NULL == GLH_EXT_NAME(glEndConditionalRenderNVX)) + return GL_FALSE; + + return GL_TRUE; + } +#endif + +#ifdef GLX_SGIX_pbuffer + if (0 == strcmp(extension, "GLX_SGIX_pbuffer")) { +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXCreateGLXPbufferSGIX) = (PFNGLXCREATEGLXPBUFFERSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXCreateGLXPbufferSGIX"); + if (NULL == GLH_EXT_NAME(glXCreateGLXPbufferSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXDestroyGLXPbufferSGIX) = (PFNGLXDESTROYGLXPBUFFERSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXDestroyGLXPbufferSGIX"); + if (NULL == GLH_EXT_NAME(glXDestroyGLXPbufferSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXQueryGLXPbufferSGIX) = (PFNGLXQUERYGLXPBUFFERSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXQueryGLXPbufferSGIX"); + if (NULL == GLH_EXT_NAME(glXQueryGLXPbufferSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXSelectEventSGIX) = (PFNGLXSELECTEVENTSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXSelectEventSGIX"); + if (NULL == GLH_EXT_NAME(glXSelectEventSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXGetSelectedEventSGIX) = (PFNGLXGETSELECTEDEVENTSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXGetSelectedEventSGIX"); + if (NULL == GLH_EXT_NAME(glXGetSelectedEventSGIX)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + +#ifdef GLX_SGIX_fbconfig + if (0 == strcmp(extension, "GLX_SGIX_fbconfig")) { +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXGetFBConfigAttribSGIX) = (PFNGLXGETFBCONFIGATTRIBSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXGetFBConfigAttribSGIX"); + if (NULL == GLH_EXT_NAME(glXGetFBConfigAttribSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXChooseFBConfigSGIX) = (PFNGLXCHOOSEFBCONFIGSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXChooseFBConfigSGIX"); + if (NULL == GLH_EXT_NAME(glXChooseFBConfigSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXCreateGLXPixmapWithConfigSGIX) = (PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXCreateGLXPixmapWithConfigSGIX"); + if (NULL == GLH_EXT_NAME(glXCreateGLXPixmapWithConfigSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXCreateContextWithConfigSGIX) = (PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXCreateContextWithConfigSGIX"); + if (NULL == GLH_EXT_NAME(glXCreateContextWithConfigSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXGetVisualFromFBConfigSGIX) = (PFNGLXGETVISUALFROMFBCONFIGSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXGetVisualFromFBConfigSGIX"); + if (NULL == GLH_EXT_NAME(glXGetVisualFromFBConfigSGIX)) + return GL_FALSE; +# endif +# ifdef GLX_VERSION_1_3 + GLH_EXT_NAME(glXGetFBConfigFromVisualSGIX) = (PFNGLXGETFBCONFIGFROMVISUALSGIXPROC)GLH_EXT_GET_PROC_ADDRESS("glXGetFBConfigFromVisualSGIX"); + if (NULL == GLH_EXT_NAME(glXGetFBConfigFromVisualSGIX)) + return GL_FALSE; +# endif + + return GL_TRUE; + } +#endif + + return GL_FALSE; +} +#endif + +#undef GLH_EXT_SINGLE_FILE + +#ifdef __cplusplus +} +#endif + +#endif /* GLH_GENEXT_H */ +#ifndef _WIN32 +#ifdef _WIN32 +#if defined(GL_VERSION_1_2) || defined(GL_VERSION_1_3) || defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ +#define glBlendColor GLH_CORE_NAME(glBlendColor) +#define glBlendEquation GLH_CORE_NAME(glBlendEquation) +#define glDrawRangeElements GLH_CORE_NAME(glDrawRangeElements) +#define glTexImage3D GLH_CORE_NAME(glTexImage3D) +#define glTexSubImage3D GLH_CORE_NAME(glTexSubImage3D) +#define glCopyTexSubImage3D GLH_CORE_NAME(glCopyTexSubImage3D) +#define glMultiTexCoord1d GLH_CORE_NAME(glMultiTexCoord1d) +#define glMultiTexCoord1dv GLH_CORE_NAME(glMultiTexCoord1dv) +#define glMultiTexCoord1f GLH_CORE_NAME(glMultiTexCoord1f) +#define glMultiTexCoord1fv GLH_CORE_NAME(glMultiTexCoord1fv) +#define glMultiTexCoord1i GLH_CORE_NAME(glMultiTexCoord1i) +#define glMultiTexCoord1iv GLH_CORE_NAME(glMultiTexCoord1iv) +#define glMultiTexCoord1s GLH_CORE_NAME(glMultiTexCoord1s) +#define glMultiTexCoord1sv GLH_CORE_NAME(glMultiTexCoord1sv) +#define glMultiTexCoord2d GLH_CORE_NAME(glMultiTexCoord2d) +#define glMultiTexCoord2dv GLH_CORE_NAME(glMultiTexCoord2dv) +#define glMultiTexCoord2f GLH_CORE_NAME(glMultiTexCoord2f) +#define glMultiTexCoord2fv GLH_CORE_NAME(glMultiTexCoord2fv) +#define glMultiTexCoord2i GLH_CORE_NAME(glMultiTexCoord2i) +#define glMultiTexCoord2iv GLH_CORE_NAME(glMultiTexCoord2iv) +#define glMultiTexCoord2s GLH_CORE_NAME(glMultiTexCoord2s) +#define glMultiTexCoord2sv GLH_CORE_NAME(glMultiTexCoord2sv) +#define glMultiTexCoord3d GLH_CORE_NAME(glMultiTexCoord3d) +#define glMultiTexCoord3dv GLH_CORE_NAME(glMultiTexCoord3dv) +#define glMultiTexCoord3f GLH_CORE_NAME(glMultiTexCoord3f) +#define glMultiTexCoord3fv GLH_CORE_NAME(glMultiTexCoord3fv) +#define glMultiTexCoord3i GLH_CORE_NAME(glMultiTexCoord3i) +#define glMultiTexCoord3iv GLH_CORE_NAME(glMultiTexCoord3iv) +#define glMultiTexCoord3s GLH_CORE_NAME(glMultiTexCoord3s) +#define glMultiTexCoord3sv GLH_CORE_NAME(glMultiTexCoord3sv) +#define glMultiTexCoord4d GLH_CORE_NAME(glMultiTexCoord4d) +#define glMultiTexCoord4dv GLH_CORE_NAME(glMultiTexCoord4dv) +#define glMultiTexCoord4f GLH_CORE_NAME(glMultiTexCoord4f) +#define glMultiTexCoord4fv GLH_CORE_NAME(glMultiTexCoord4fv) +#define glMultiTexCoord4i GLH_CORE_NAME(glMultiTexCoord4i) +#define glMultiTexCoord4iv GLH_CORE_NAME(glMultiTexCoord4iv) +#define glMultiTexCoord4s GLH_CORE_NAME(glMultiTexCoord4s) +#define glMultiTexCoord4sv GLH_CORE_NAME(glMultiTexCoord4sv) +#define glActiveTexture GLH_CORE_NAME(glActiveTexture) +#define glClientActiveTexture GLH_CORE_NAME(glClientActiveTexture) +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_3) || defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ +#define glCompressedTexImage3D GLH_CORE_NAME(glCompressedTexImage3D) +#define glCompressedTexImage2D GLH_CORE_NAME(glCompressedTexImage2D) +#define glCompressedTexImage1D GLH_CORE_NAME(glCompressedTexImage1D) +#define glCompressedTexSubImage3D GLH_CORE_NAME(glCompressedTexSubImage3D) +#define glCompressedTexSubImage2D GLH_CORE_NAME(glCompressedTexSubImage2D) +#define glCompressedTexSubImage1D GLH_CORE_NAME(glCompressedTexSubImage1D) +#define glGetCompressedTexImage GLH_CORE_NAME(glGetCompressedTexImage) +#define glSampleCoverage GLH_CORE_NAME(glSampleCoverage) +#define glLoadTransposeMatrixf GLH_CORE_NAME(glLoadTransposeMatrixf) +#define glLoadTransposeMatrixd GLH_CORE_NAME(glLoadTransposeMatrixd) +#define glMultTransposeMatrixf GLH_CORE_NAME(glMultTransposeMatrixf) +#define glMultTransposeMatrixd GLH_CORE_NAME(glMultTransposeMatrixd) +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_4) || defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ +#define glMultiDrawArrays GLH_CORE_NAME(glMultiDrawArrays) +#define glMultiDrawElements GLH_CORE_NAME(glMultiDrawElements) +#define glPointParameterf GLH_CORE_NAME(glPointParameterf) +#define glPointParameterfv GLH_CORE_NAME(glPointParameterfv) +#define glSecondaryColor3b GLH_CORE_NAME(glSecondaryColor3b) +#define glSecondaryColor3bv GLH_CORE_NAME(glSecondaryColor3bv) +#define glSecondaryColor3d GLH_CORE_NAME(glSecondaryColor3d) +#define glSecondaryColor3dv GLH_CORE_NAME(glSecondaryColor3dv) +#define glSecondaryColor3f GLH_CORE_NAME(glSecondaryColor3f) +#define glSecondaryColor3fv GLH_CORE_NAME(glSecondaryColor3fv) +#define glSecondaryColor3i GLH_CORE_NAME(glSecondaryColor3i) +#define glSecondaryColor3iv GLH_CORE_NAME(glSecondaryColor3iv) +#define glSecondaryColor3s GLH_CORE_NAME(glSecondaryColor3s) +#define glSecondaryColor3sv GLH_CORE_NAME(glSecondaryColor3sv) +#define glSecondaryColor3ub GLH_CORE_NAME(glSecondaryColor3ub) +#define glSecondaryColor3ubv GLH_CORE_NAME(glSecondaryColor3ubv) +#define glSecondaryColor3ui GLH_CORE_NAME(glSecondaryColor3ui) +#define glSecondaryColor3uiv GLH_CORE_NAME(glSecondaryColor3uiv) +#define glSecondaryColor3us GLH_CORE_NAME(glSecondaryColor3us) +#define glSecondaryColor3usv GLH_CORE_NAME(glSecondaryColor3usv) +#define glSecondaryColorPointer GLH_CORE_NAME(glSecondaryColorPointer) +#define glBlendFuncSeparate GLH_CORE_NAME(glBlendFuncSeparate) +#define glWindowPos2d GLH_CORE_NAME(glWindowPos2d) +#define glWindowPos2f GLH_CORE_NAME(glWindowPos2f) +#define glWindowPos2i GLH_CORE_NAME(glWindowPos2i) +#define glWindowPos2s GLH_CORE_NAME(glWindowPos2s) +#define glWindowPos2dv GLH_CORE_NAME(glWindowPos2dv) +#define glWindowPos2fv GLH_CORE_NAME(glWindowPos2fv) +#define glWindowPos2iv GLH_CORE_NAME(glWindowPos2iv) +#define glWindowPos2sv GLH_CORE_NAME(glWindowPos2sv) +#define glWindowPos3d GLH_CORE_NAME(glWindowPos3d) +#define glWindowPos3f GLH_CORE_NAME(glWindowPos3f) +#define glWindowPos3i GLH_CORE_NAME(glWindowPos3i) +#define glWindowPos3s GLH_CORE_NAME(glWindowPos3s) +#define glWindowPos3dv GLH_CORE_NAME(glWindowPos3dv) +#define glWindowPos3fv GLH_CORE_NAME(glWindowPos3fv) +#define glWindowPos3iv GLH_CORE_NAME(glWindowPos3iv) +#define glWindowPos3sv GLH_CORE_NAME(glWindowPos3sv) +#endif +#endif + +#ifdef _WIN32 +#if defined(GL_VERSION_1_5) + /* These routines are prefixed by the preprocessor constant + GLH_CORE_PREFIX to avoid colliding with the OpenGL 1.1 namespace. */ +#define glGenQueries GLH_CORE_NAME(glGenQueries) +#define glDeleteQueries GLH_CORE_NAME(glDeleteQueries) +#define glIsQuery GLH_CORE_NAME(glIsQuery) +#define glBeginQuery GLH_CORE_NAME(glBeginQuery) +#define glEndQuery GLH_CORE_NAME(glEndQuery) +#define glGetQueryiv GLH_CORE_NAME(glGetQueryiv) +#define glGetQueryObjectiv GLH_CORE_NAME(glGetQueryObjectiv) +#define glGetQueryObjectuiv GLH_CORE_NAME(glGetQueryObjectuiv) +#define glBindBuffer GLH_CORE_NAME(glBindBuffer) +#define glDeleteBuffers GLH_CORE_NAME(glDeleteBuffers) +#define glGenBuffers GLH_CORE_NAME(glGenBuffers) +#define glIsBuffer GLH_CORE_NAME(glIsBuffer) +#define glBufferData GLH_CORE_NAME(glBufferData) +#define glBufferSubData GLH_CORE_NAME(glBufferSubData) +#define glGetBufferSubData GLH_CORE_NAME(glGetBufferSubData) +#define glMapBuffer GLH_CORE_NAME(glMapBuffer) +#define glUnmapBuffer GLH_CORE_NAME(glUnmapBuffer) +#define glGetBufferParameteriv GLH_CORE_NAME(glGetBufferParameteriv) +#define glGetBufferPointerv GLH_CORE_NAME(glGetBufferPointerv) +#endif +#endif + +#ifdef GL_ARB_depth_texture +#endif + +#ifdef GL_ARB_fragment_program +#endif + +#ifdef GL_ARB_fragment_program_shadow +#endif + +#ifdef GL_ARB_fragment_shader +#endif + +#ifdef GL_ARB_matrix_palette +#endif + +#ifdef GL_ARB_multisample +#endif + +#ifdef GL_ARB_multitexture +#define glMultiTexCoord1dARB GLH_EXT_NAME(glMultiTexCoord1dARB) +#define glMultiTexCoord1dvARB GLH_EXT_NAME(glMultiTexCoord1dvARB) +#define glMultiTexCoord1fARB GLH_EXT_NAME(glMultiTexCoord1fARB) +#define glMultiTexCoord1fvARB GLH_EXT_NAME(glMultiTexCoord1fvARB) +#define glMultiTexCoord1iARB GLH_EXT_NAME(glMultiTexCoord1iARB) +#define glMultiTexCoord1ivARB GLH_EXT_NAME(glMultiTexCoord1ivARB) +#define glMultiTexCoord1sARB GLH_EXT_NAME(glMultiTexCoord1sARB) +#define glMultiTexCoord1svARB GLH_EXT_NAME(glMultiTexCoord1svARB) +#define glMultiTexCoord2dARB GLH_EXT_NAME(glMultiTexCoord2dARB) +#define glMultiTexCoord2dvARB GLH_EXT_NAME(glMultiTexCoord2dvARB) +#define glMultiTexCoord2fARB GLH_EXT_NAME(glMultiTexCoord2fARB) +#define glMultiTexCoord2fvARB GLH_EXT_NAME(glMultiTexCoord2fvARB) +#define glMultiTexCoord2iARB GLH_EXT_NAME(glMultiTexCoord2iARB) +#define glMultiTexCoord2ivARB GLH_EXT_NAME(glMultiTexCoord2ivARB) +#define glMultiTexCoord2sARB GLH_EXT_NAME(glMultiTexCoord2sARB) +#define glMultiTexCoord2svARB GLH_EXT_NAME(glMultiTexCoord2svARB) +#define glMultiTexCoord3dARB GLH_EXT_NAME(glMultiTexCoord3dARB) +#define glMultiTexCoord3dvARB GLH_EXT_NAME(glMultiTexCoord3dvARB) +#define glMultiTexCoord3fARB GLH_EXT_NAME(glMultiTexCoord3fARB) +#define glMultiTexCoord3fvARB GLH_EXT_NAME(glMultiTexCoord3fvARB) +#define glMultiTexCoord3iARB GLH_EXT_NAME(glMultiTexCoord3iARB) +#define glMultiTexCoord3ivARB GLH_EXT_NAME(glMultiTexCoord3ivARB) +#define glMultiTexCoord3sARB GLH_EXT_NAME(glMultiTexCoord3sARB) +#define glMultiTexCoord3svARB GLH_EXT_NAME(glMultiTexCoord3svARB) +#define glMultiTexCoord4dARB GLH_EXT_NAME(glMultiTexCoord4dARB) +#define glMultiTexCoord4dvARB GLH_EXT_NAME(glMultiTexCoord4dvARB) +#define glMultiTexCoord4fARB GLH_EXT_NAME(glMultiTexCoord4fARB) +#define glMultiTexCoord4fvARB GLH_EXT_NAME(glMultiTexCoord4fvARB) +#define glMultiTexCoord4iARB GLH_EXT_NAME(glMultiTexCoord4iARB) +#define glMultiTexCoord4ivARB GLH_EXT_NAME(glMultiTexCoord4ivARB) +#define glMultiTexCoord4sARB GLH_EXT_NAME(glMultiTexCoord4sARB) +#define glMultiTexCoord4svARB GLH_EXT_NAME(glMultiTexCoord4svARB) +#define glActiveTextureARB GLH_EXT_NAME(glActiveTextureARB) +#define glClientActiveTextureARB GLH_EXT_NAME(glClientActiveTextureARB) +#endif + +#ifdef GL_ARB_occlusion_query +#define glGenQueriesARB GLH_EXT_NAME(glGenQueriesARB) +#define glDeleteQueriesARB GLH_EXT_NAME(glDeleteQueriesARB) +#define glIsQueryARB GLH_EXT_NAME(glIsQueryARB) +#define glBeginQueryARB GLH_EXT_NAME(glBeginQueryARB) +#define glEndQueryARB GLH_EXT_NAME(glEndQueryARB) +#define glGetQueryivARB GLH_EXT_NAME(glGetQueryivARB) +#define glGetQueryObjectivARB GLH_EXT_NAME(glGetQueryObjectivARB) +#define glGetQueryObjectuivARB GLH_EXT_NAME(glGetQueryObjectuivARB) +#endif + +#ifdef GL_ARB_point_parameters +#define glPointParameterfARB GLH_EXT_NAME(glPointParameterfARB) +#define glPointParameterfvARB GLH_EXT_NAME(glPointParameterfvARB) +#endif + +#ifdef GL_ARB_point_sprite +#endif + +#ifdef GL_ARB_shader_objects +#define glDeleteObjectARB GLH_EXT_NAME(glDeleteObjectARB) +#define glGetHandleARB GLH_EXT_NAME(glGetHandleARB) +#define glDetachObjectARB GLH_EXT_NAME(glDetachObjectARB) +#define glCreateShaderObjectARB GLH_EXT_NAME(glCreateShaderObjectARB) +#define glShaderSourceARB GLH_EXT_NAME(glShaderSourceARB) +#define glCompileShaderARB GLH_EXT_NAME(glCompileShaderARB) +#define glCreateProgramObjectARB GLH_EXT_NAME(glCreateProgramObjectARB) +#define glAttachObjectARB GLH_EXT_NAME(glAttachObjectARB) +#define glLinkProgramARB GLH_EXT_NAME(glLinkProgramARB) +#define glUseProgramObjectARB GLH_EXT_NAME(glUseProgramObjectARB) +#define glValidateProgramARB GLH_EXT_NAME(glValidateProgramARB) +#define glUniform1fARB GLH_EXT_NAME(glUniform1fARB) +#define glUniform2fARB GLH_EXT_NAME(glUniform2fARB) +#define glUniform3fARB GLH_EXT_NAME(glUniform3fARB) +#define glUniform4fARB GLH_EXT_NAME(glUniform4fARB) +#define glUniform1iARB GLH_EXT_NAME(glUniform1iARB) +#define glUniform2iARB GLH_EXT_NAME(glUniform2iARB) +#define glUniform3iARB GLH_EXT_NAME(glUniform3iARB) +#define glUniform4iARB GLH_EXT_NAME(glUniform4iARB) +#define glUniform1fvARB GLH_EXT_NAME(glUniform1fvARB) +#define glUniform2fvARB GLH_EXT_NAME(glUniform2fvARB) +#define glUniform3fvARB GLH_EXT_NAME(glUniform3fvARB) +#define glUniform4fvARB GLH_EXT_NAME(glUniform4fvARB) +#define glUniform1ivARB GLH_EXT_NAME(glUniform1ivARB) +#define glUniform2ivARB GLH_EXT_NAME(glUniform2ivARB) +#define glUniform3ivARB GLH_EXT_NAME(glUniform3ivARB) +#define glUniform4ivARB GLH_EXT_NAME(glUniform4ivARB) +#define glUniformMatrix2fvARB GLH_EXT_NAME(glUniformMatrix2fvARB) +#define glUniformMatrix3fvARB GLH_EXT_NAME(glUniformMatrix3fvARB) +#define glUniformMatrix4fvARB GLH_EXT_NAME(glUniformMatrix4fvARB) +#define glGetObjectParameterfvARB GLH_EXT_NAME(glGetObjectParameterfvARB) +#define glGetObjectParameterivARB GLH_EXT_NAME(glGetObjectParameterivARB) +#define glGetInfoLogARB GLH_EXT_NAME(glGetInfoLogARB) +#define glGetAttachedObjectsARB GLH_EXT_NAME(glGetAttachedObjectsARB) +#define glGetUniformLocationARB GLH_EXT_NAME(glGetUniformLocationARB) +#define glGetActiveUniformARB GLH_EXT_NAME(glGetActiveUniformARB) +#define glGetUniformfvARB GLH_EXT_NAME(glGetUniformfvARB) +#define glGetUniformivARB GLH_EXT_NAME(glGetUniformivARB) +#define glGetShaderSourceARB GLH_EXT_NAME(glGetShaderSourceARB) +#endif + +#ifdef GL_ARB_shadow +#endif + +#ifdef GL_ARB_shadow_ambient +#endif + +#ifdef GL_ARB_texture_border_clamp +#endif + +#ifdef GL_ARB_texture_compression +#define glCompressedTexImage3DARB GLH_EXT_NAME(glCompressedTexImage3DARB) +#define glCompressedTexImage2DARB GLH_EXT_NAME(glCompressedTexImage2DARB) +#define glCompressedTexImage1DARB GLH_EXT_NAME(glCompressedTexImage1DARB) +#define glCompressedTexSubImage3DARB GLH_EXT_NAME(glCompressedTexSubImage3DARB) +#define glCompressedTexSubImage2DARB GLH_EXT_NAME(glCompressedTexSubImage2DARB) +#define glCompressedTexSubImage1DARB GLH_EXT_NAME(glCompressedTexSubImage1DARB) +#define glGetCompressedTexImageARB GLH_EXT_NAME(glGetCompressedTexImageARB) +#endif + +#ifdef GL_ARB_texture_cube_map +#endif + +#ifdef GL_ARB_texture_env_add +#endif + +#ifdef GL_ARB_texture_env_combine +#endif + +#ifdef GL_ARB_texture_env_dot3 +#endif + +#ifdef GL_ARB_texture_mirrored_repeat +#endif + +#ifdef GL_ARB_texture_non_power_of_two +#endif + +#ifdef GL_ARB_texture_rectangle +#endif + +#ifdef GL_ARB_transpose_matrix +#define glLoadTransposeMatrixfARB GLH_EXT_NAME(glLoadTransposeMatrixfARB) +#define glLoadTransposeMatrixdARB GLH_EXT_NAME(glLoadTransposeMatrixdARB) +#define glMultTransposeMatrixfARB GLH_EXT_NAME(glMultTransposeMatrixfARB) +#define glMultTransposeMatrixdARB GLH_EXT_NAME(glMultTransposeMatrixdARB) +#endif + +#ifdef GL_ARB_vertex_buffer_object +#define glBindBufferARB GLH_EXT_NAME(glBindBufferARB) +#define glDeleteBuffersARB GLH_EXT_NAME(glDeleteBuffersARB) +#define glGenBuffersARB GLH_EXT_NAME(glGenBuffersARB) +#define glIsBufferARB GLH_EXT_NAME(glIsBufferARB) +#define glBufferDataARB GLH_EXT_NAME(glBufferDataARB) +#define glBufferSubDataARB GLH_EXT_NAME(glBufferSubDataARB) +#define glGetBufferSubDataARB GLH_EXT_NAME(glGetBufferSubDataARB) +#define glMapBufferARB GLH_EXT_NAME(glMapBufferARB) +#define glUnmapBufferARB GLH_EXT_NAME(glUnmapBufferARB) +#define glGetBufferParameterivARB GLH_EXT_NAME(glGetBufferParameterivARB) +#define glGetBufferPointervARB GLH_EXT_NAME(glGetBufferPointervARB) +#endif + +#ifdef GL_ARB_vertex_program +#define glVertexAttrib1sARB GLH_EXT_NAME(glVertexAttrib1sARB) +#define glVertexAttrib1fARB GLH_EXT_NAME(glVertexAttrib1fARB) +#define glVertexAttrib1dARB GLH_EXT_NAME(glVertexAttrib1dARB) +#define glVertexAttrib2sARB GLH_EXT_NAME(glVertexAttrib2sARB) +#define glVertexAttrib2fARB GLH_EXT_NAME(glVertexAttrib2fARB) +#define glVertexAttrib2dARB GLH_EXT_NAME(glVertexAttrib2dARB) +#define glVertexAttrib3sARB GLH_EXT_NAME(glVertexAttrib3sARB) +#define glVertexAttrib3fARB GLH_EXT_NAME(glVertexAttrib3fARB) +#define glVertexAttrib3dARB GLH_EXT_NAME(glVertexAttrib3dARB) +#define glVertexAttrib4sARB GLH_EXT_NAME(glVertexAttrib4sARB) +#define glVertexAttrib4fARB GLH_EXT_NAME(glVertexAttrib4fARB) +#define glVertexAttrib4dARB GLH_EXT_NAME(glVertexAttrib4dARB) +#define glVertexAttrib4NubARB GLH_EXT_NAME(glVertexAttrib4NubARB) +#define glVertexAttrib1svARB GLH_EXT_NAME(glVertexAttrib1svARB) +#define glVertexAttrib1fvARB GLH_EXT_NAME(glVertexAttrib1fvARB) +#define glVertexAttrib1dvARB GLH_EXT_NAME(glVertexAttrib1dvARB) +#define glVertexAttrib2svARB GLH_EXT_NAME(glVertexAttrib2svARB) +#define glVertexAttrib2fvARB GLH_EXT_NAME(glVertexAttrib2fvARB) +#define glVertexAttrib2dvARB GLH_EXT_NAME(glVertexAttrib2dvARB) +#define glVertexAttrib3svARB GLH_EXT_NAME(glVertexAttrib3svARB) +#define glVertexAttrib3fvARB GLH_EXT_NAME(glVertexAttrib3fvARB) +#define glVertexAttrib3dvARB GLH_EXT_NAME(glVertexAttrib3dvARB) +#define glVertexAttrib4bvARB GLH_EXT_NAME(glVertexAttrib4bvARB) +#define glVertexAttrib4svARB GLH_EXT_NAME(glVertexAttrib4svARB) +#define glVertexAttrib4ivARB GLH_EXT_NAME(glVertexAttrib4ivARB) +#define glVertexAttrib4ubvARB GLH_EXT_NAME(glVertexAttrib4ubvARB) +#define glVertexAttrib4usvARB GLH_EXT_NAME(glVertexAttrib4usvARB) +#define glVertexAttrib4uivARB GLH_EXT_NAME(glVertexAttrib4uivARB) +#define glVertexAttrib4fvARB GLH_EXT_NAME(glVertexAttrib4fvARB) +#define glVertexAttrib4dvARB GLH_EXT_NAME(glVertexAttrib4dvARB) +#define glVertexAttrib4NbvARB GLH_EXT_NAME(glVertexAttrib4NbvARB) +#define glVertexAttrib4NsvARB GLH_EXT_NAME(glVertexAttrib4NsvARB) +#define glVertexAttrib4NivARB GLH_EXT_NAME(glVertexAttrib4NivARB) +#define glVertexAttrib4NubvARB GLH_EXT_NAME(glVertexAttrib4NubvARB) +#define glVertexAttrib4NusvARB GLH_EXT_NAME(glVertexAttrib4NusvARB) +#define glVertexAttrib4NuivARB GLH_EXT_NAME(glVertexAttrib4NuivARB) +#define glVertexAttribPointerARB GLH_EXT_NAME(glVertexAttribPointerARB) +#define glEnableVertexAttribArrayARB GLH_EXT_NAME(glEnableVertexAttribArrayARB) +#define glDisableVertexAttribArrayARB GLH_EXT_NAME(glDisableVertexAttribArrayARB) +#define glProgramStringARB GLH_EXT_NAME(glProgramStringARB) +#define glBindProgramARB GLH_EXT_NAME(glBindProgramARB) +#define glDeleteProgramsARB GLH_EXT_NAME(glDeleteProgramsARB) +#define glGenProgramsARB GLH_EXT_NAME(glGenProgramsARB) +#define glProgramEnvParameter4dARB GLH_EXT_NAME(glProgramEnvParameter4dARB) +#define glProgramEnvParameter4dvARB GLH_EXT_NAME(glProgramEnvParameter4dvARB) +#define glProgramEnvParameter4fARB GLH_EXT_NAME(glProgramEnvParameter4fARB) +#define glProgramEnvParameter4fvARB GLH_EXT_NAME(glProgramEnvParameter4fvARB) +#define glProgramLocalParameter4dARB GLH_EXT_NAME(glProgramLocalParameter4dARB) +#define glProgramLocalParameter4dvARB GLH_EXT_NAME(glProgramLocalParameter4dvARB) +#define glProgramLocalParameter4fARB GLH_EXT_NAME(glProgramLocalParameter4fARB) +#define glProgramLocalParameter4fvARB GLH_EXT_NAME(glProgramLocalParameter4fvARB) +#define glGetProgramEnvParameterdvARB GLH_EXT_NAME(glGetProgramEnvParameterdvARB) +#define glGetProgramEnvParameterfvARB GLH_EXT_NAME(glGetProgramEnvParameterfvARB) +#define glGetProgramLocalParameterdvARB GLH_EXT_NAME(glGetProgramLocalParameterdvARB) +#define glGetProgramLocalParameterfvARB GLH_EXT_NAME(glGetProgramLocalParameterfvARB) +#define glGetProgramivARB GLH_EXT_NAME(glGetProgramivARB) +#define glGetProgramStringARB GLH_EXT_NAME(glGetProgramStringARB) +#define glGetVertexAttribdvARB GLH_EXT_NAME(glGetVertexAttribdvARB) +#define glGetVertexAttribfvARB GLH_EXT_NAME(glGetVertexAttribfvARB) +#define glGetVertexAttribivARB GLH_EXT_NAME(glGetVertexAttribivARB) +#define glGetVertexAttribPointervARB GLH_EXT_NAME(glGetVertexAttribPointervARB) +#define glIsProgramARB GLH_EXT_NAME(glIsProgramARB) +#endif + +#ifdef GL_ARB_vertex_shader +#define glBindAttribLocationARB GLH_EXT_NAME(glBindAttribLocationARB) +#define glGetActiveAttribARB GLH_EXT_NAME(glGetActiveAttribARB) +#define glGetAttribLocationARB GLH_EXT_NAME(glGetAttribLocationARB) +#endif + +#ifdef GL_ARB_window_pos +#define glWindowPos2dARB GLH_EXT_NAME(glWindowPos2dARB) +#define glWindowPos2fARB GLH_EXT_NAME(glWindowPos2fARB) +#define glWindowPos2iARB GLH_EXT_NAME(glWindowPos2iARB) +#define glWindowPos2sARB GLH_EXT_NAME(glWindowPos2sARB) +#define glWindowPos2dvARB GLH_EXT_NAME(glWindowPos2dvARB) +#define glWindowPos2fvARB GLH_EXT_NAME(glWindowPos2fvARB) +#define glWindowPos2ivARB GLH_EXT_NAME(glWindowPos2ivARB) +#define glWindowPos2svARB GLH_EXT_NAME(glWindowPos2svARB) +#define glWindowPos3dARB GLH_EXT_NAME(glWindowPos3dARB) +#define glWindowPos3fARB GLH_EXT_NAME(glWindowPos3fARB) +#define glWindowPos3iARB GLH_EXT_NAME(glWindowPos3iARB) +#define glWindowPos3sARB GLH_EXT_NAME(glWindowPos3sARB) +#define glWindowPos3dvARB GLH_EXT_NAME(glWindowPos3dvARB) +#define glWindowPos3fvARB GLH_EXT_NAME(glWindowPos3fvARB) +#define glWindowPos3ivARB GLH_EXT_NAME(glWindowPos3ivARB) +#define glWindowPos3svARB GLH_EXT_NAME(glWindowPos3svARB) +#endif + +#ifdef GL_ATI_draw_buffers +#define glDrawBuffersATI GLH_EXT_NAME(glDrawBuffersATI) +#endif + +#ifdef GL_ATI_texture_float +#endif + +#ifdef GL_EXT_abgr +#endif + +#ifdef GL_EXT_bgra +#endif + +#ifdef GL_EXT_blend_color +#define glBlendColorEXT GLH_EXT_NAME(glBlendColorEXT) +#endif + +#ifdef GL_EXT_blend_equation_separate +#define glBlendEquationSeparateEXT GLH_EXT_NAME(glBlendEquationSeparateEXT) +#endif + +#ifdef GL_EXT_blend_func_separate +#define glBlendFuncSeparateEXT GLH_EXT_NAME(glBlendFuncSeparateEXT) +#endif + +#ifdef GL_EXT_blend_minmax +#define glBlendEquationEXT GLH_EXT_NAME(glBlendEquationEXT) +#endif + +#ifdef GL_EXT_blend_subtract +#endif + +#ifdef GL_EXT_Cg_shader +#endif + +#ifdef GL_EXT_compiled_vertex_array +#define glLockArraysEXT GLH_EXT_NAME(glLockArraysEXT) +#define glUnlockArraysEXT GLH_EXT_NAME(glUnlockArraysEXT) +#endif + +#ifdef GL_EXT_depth_bounds_test +#define glDepthBoundsEXT GLH_EXT_NAME(glDepthBoundsEXT) +#endif + +#ifdef GL_EXT_draw_range_elements +#endif + +#ifdef GL_EXT_fog_coord +#define glFogCoorddEXT GLH_EXT_NAME(glFogCoorddEXT) +#define glFogCoorddvEXT GLH_EXT_NAME(glFogCoorddvEXT) +#define glFogCoordfEXT GLH_EXT_NAME(glFogCoordfEXT) +#define glFogCoordfvEXT GLH_EXT_NAME(glFogCoordfvEXT) +#define glFogCoordPointerEXT GLH_EXT_NAME(glFogCoordPointerEXT) +#endif + +#ifdef GL_EXT_framebuffer_object +#define glIsRenderbufferEXT GLH_EXT_NAME(glIsRenderbufferEXT) +#define glBindRenderbufferEXT GLH_EXT_NAME(glBindRenderbufferEXT) +#define glDeleteRenderbuffersEXT GLH_EXT_NAME(glDeleteRenderbuffersEXT) +#define glGenRenderbuffersEXT GLH_EXT_NAME(glGenRenderbuffersEXT) +#define glRenderbufferStorageEXT GLH_EXT_NAME(glRenderbufferStorageEXT) +#define glGetRenderbufferParameterivEXT GLH_EXT_NAME(glGetRenderbufferParameterivEXT) +#define glIsFramebufferEXT GLH_EXT_NAME(glIsFramebufferEXT) +#define glBindFramebufferEXT GLH_EXT_NAME(glBindFramebufferEXT) +#define glDeleteFramebuffersEXT GLH_EXT_NAME(glDeleteFramebuffersEXT) +#define glGenFramebuffersEXT GLH_EXT_NAME(glGenFramebuffersEXT) +#define glCheckFramebufferStatusEXT GLH_EXT_NAME(glCheckFramebufferStatusEXT) +#define glFramebufferTexture1DEXT GLH_EXT_NAME(glFramebufferTexture1DEXT) +#define glFramebufferTexture2DEXT GLH_EXT_NAME(glFramebufferTexture2DEXT) +#define glFramebufferTexture3DEXT GLH_EXT_NAME(glFramebufferTexture3DEXT) +#define glFramebufferRenderbufferEXT GLH_EXT_NAME(glFramebufferRenderbufferEXT) +#define glGetFramebufferAttachmentParameterivEXT GLH_EXT_NAME(glGetFramebufferAttachmentParameterivEXT) +#define glGenerateMipmapEXT GLH_EXT_NAME(glGenerateMipmapEXT) +#endif + +#ifdef GL_EXT_multi_draw_arrays +#define glMultiDrawArraysEXT GLH_EXT_NAME(glMultiDrawArraysEXT) +#define glMultiDrawElementsEXT GLH_EXT_NAME(glMultiDrawElementsEXT) +#endif + +#ifdef GL_EXT_light_max_exponent +#endif + +#ifdef GL_EXT_packed_pixels +#endif + +#ifdef GL_EXT_paletted_texture +#define glColorSubTableEXT GLH_EXT_NAME(glColorSubTableEXT) +#define glColorTableEXT GLH_EXT_NAME(glColorTableEXT) +#define glGetColorTableEXT GLH_EXT_NAME(glGetColorTableEXT) +#define glGetColorTableParameterfvEXT GLH_EXT_NAME(glGetColorTableParameterfvEXT) +#define glGetColorTableParameterivEXT GLH_EXT_NAME(glGetColorTableParameterivEXT) +#endif + +#ifdef GL_EXT_pixel_buffer_object +#endif + +#ifdef GL_EXT_point_parameters +#define glPointParameterfEXT GLH_EXT_NAME(glPointParameterfEXT) +#define glPointParameterfvEXT GLH_EXT_NAME(glPointParameterfvEXT) +#endif + +#ifdef GL_EXT_rescale_normal +#endif + +#ifdef GL_EXT_secondary_color +#define glSecondaryColor3bEXT GLH_EXT_NAME(glSecondaryColor3bEXT) +#define glSecondaryColor3bvEXT GLH_EXT_NAME(glSecondaryColor3bvEXT) +#define glSecondaryColor3dEXT GLH_EXT_NAME(glSecondaryColor3dEXT) +#define glSecondaryColor3dvEXT GLH_EXT_NAME(glSecondaryColor3dvEXT) +#define glSecondaryColor3fEXT GLH_EXT_NAME(glSecondaryColor3fEXT) +#define glSecondaryColor3fvEXT GLH_EXT_NAME(glSecondaryColor3fvEXT) +#define glSecondaryColor3iEXT GLH_EXT_NAME(glSecondaryColor3iEXT) +#define glSecondaryColor3ivEXT GLH_EXT_NAME(glSecondaryColor3ivEXT) +#define glSecondaryColor3sEXT GLH_EXT_NAME(glSecondaryColor3sEXT) +#define glSecondaryColor3svEXT GLH_EXT_NAME(glSecondaryColor3svEXT) +#define glSecondaryColor3ubEXT GLH_EXT_NAME(glSecondaryColor3ubEXT) +#define glSecondaryColor3ubvEXT GLH_EXT_NAME(glSecondaryColor3ubvEXT) +#define glSecondaryColor3uiEXT GLH_EXT_NAME(glSecondaryColor3uiEXT) +#define glSecondaryColor3uivEXT GLH_EXT_NAME(glSecondaryColor3uivEXT) +#define glSecondaryColor3usEXT GLH_EXT_NAME(glSecondaryColor3usEXT) +#define glSecondaryColor3usvEXT GLH_EXT_NAME(glSecondaryColor3usvEXT) +#define glSecondaryColorPointerEXT GLH_EXT_NAME(glSecondaryColorPointerEXT) +#endif + +#ifdef GL_EXT_separate_specular_color +#endif + +#ifdef GL_EXT_shadow_funcs +#endif + +#ifdef GL_EXT_shared_texture_palette +#endif + +#ifdef GL_EXT_stencil_two_side +#define glActiveStencilFaceEXT GLH_EXT_NAME(glActiveStencilFaceEXT) +#endif + +#ifdef GL_EXT_stencil_wrap +#endif + +#ifdef GL_EXT_texture_compression_s3tc +#endif + +#ifdef GL_EXT_texture_cube_map +#endif + +#ifdef GL_EXT_texture_edge_clamp +#endif + +#ifdef GL_EXT_texture_env_add +#endif + +#ifdef GL_EXT_texture_env_combine +#endif + +#ifdef GL_EXT_texture_env_dot3 +#endif + +#ifdef GL_EXT_texture_filter_anisotropic +#endif + +#ifdef GL_EXT_texture_lod_bias +#endif + +#ifdef GL_EXT_texture_object +#define glAreTexturesResidentEXT GLH_EXT_NAME(glAreTexturesResidentEXT) +#define glBindTextureEXT GLH_EXT_NAME(glBindTextureEXT) +#define glDeleteTexturesEXT GLH_EXT_NAME(glDeleteTexturesEXT) +#define glGenTexturesEXT GLH_EXT_NAME(glGenTexturesEXT) +#define glIsTextureEXT GLH_EXT_NAME(glIsTextureEXT) +#define glPrioritizeTexturesEXT GLH_EXT_NAME(glPrioritizeTexturesEXT) +#endif + +#ifdef GL_EXT_texture_rectangle +#endif + +#ifdef GL_EXT_texture3D +#define glTexImage3DEXT GLH_EXT_NAME(glTexImage3DEXT) +#endif + +#ifdef GL_EXT_vertex_array +#define glArrayElementEXT GLH_EXT_NAME(glArrayElementEXT) +#define glColorPointerEXT GLH_EXT_NAME(glColorPointerEXT) +#define glEdgeFlagPointerEXT GLH_EXT_NAME(glEdgeFlagPointerEXT) +#define glGetPointervEXT GLH_EXT_NAME(glGetPointervEXT) +#define glIndexPointerEXT GLH_EXT_NAME(glIndexPointerEXT) +#define glNormalPointerEXT GLH_EXT_NAME(glNormalPointerEXT) +#define glTexCoordPointerEXT GLH_EXT_NAME(glTexCoordPointerEXT) +#define glVertexPointerEXT GLH_EXT_NAME(glVertexPointerEXT) +#define glDrawArraysEXT GLH_EXT_NAME(glDrawArraysEXT) +#endif + +#ifdef GL_EXT_vertex_weighting +#define glVertexWeightfEXT GLH_EXT_NAME(glVertexWeightfEXT) +#define glVertexWeightfvEXT GLH_EXT_NAME(glVertexWeightfvEXT) +#define glVertexWeightPointerEXT GLH_EXT_NAME(glVertexWeightPointerEXT) +#endif + +#ifdef GL_HP_occlusion_test +#endif + +#ifdef GL_IBM_texture_mirrored_repeat +#endif + +#ifdef GL_NV_blend_square +#endif + +#ifdef GL_NV_copy_depth_to_color +#endif + +#ifdef GL_NV_depth_clamp +#endif + +#ifdef GL_NV_element_array +#define glElementPointerNV GLH_EXT_NAME(glElementPointerNV) +#define glDrawElementArrayNV GLH_EXT_NAME(glDrawElementArrayNV) +#define glDrawRangeElementArrayNV GLH_EXT_NAME(glDrawRangeElementArrayNV) +#define glMultiDrawElementArrayNV GLH_EXT_NAME(glMultiDrawElementArrayNV) +#define glMultiDrawRangeElementArrayNV GLH_EXT_NAME(glMultiDrawRangeElementArrayNV) +#endif + +#ifdef GL_NV_fence +#define glGenFencesNV GLH_EXT_NAME(glGenFencesNV) +#define glDeleteFencesNV GLH_EXT_NAME(glDeleteFencesNV) +#define glSetFenceNV GLH_EXT_NAME(glSetFenceNV) +#define glTestFenceNV GLH_EXT_NAME(glTestFenceNV) +#define glFinishFenceNV GLH_EXT_NAME(glFinishFenceNV) +#define glIsFenceNV GLH_EXT_NAME(glIsFenceNV) +#define glGetFenceivNV GLH_EXT_NAME(glGetFenceivNV) +#endif + +#ifdef GL_NV_float_buffer +#endif + +#ifdef GL_NV_fog_distance +#endif + +#ifdef GL_NV_fragment_program +#define glProgramNamedParameter4fNV GLH_EXT_NAME(glProgramNamedParameter4fNV) +#define glProgramNamedParameter4dNV GLH_EXT_NAME(glProgramNamedParameter4dNV) +#define glProgramNamedParameter4fvNV GLH_EXT_NAME(glProgramNamedParameter4fvNV) +#define glProgramNamedParameter4dvNV GLH_EXT_NAME(glProgramNamedParameter4dvNV) +#define glGetProgramNamedParameterfvNV GLH_EXT_NAME(glGetProgramNamedParameterfvNV) +#define glGetProgramNamedParameterdvNV GLH_EXT_NAME(glGetProgramNamedParameterdvNV) +#endif + +#ifdef GL_NV_fragment_program2 +#endif + +#ifdef GL_NV_half_float +#define glVertex2hNV GLH_EXT_NAME(glVertex2hNV) +#define glVertex2hvNV GLH_EXT_NAME(glVertex2hvNV) +#define glVertex3hNV GLH_EXT_NAME(glVertex3hNV) +#define glVertex3hvNV GLH_EXT_NAME(glVertex3hvNV) +#define glVertex4hNV GLH_EXT_NAME(glVertex4hNV) +#define glVertex4hvNV GLH_EXT_NAME(glVertex4hvNV) +#define glNormal3hNV GLH_EXT_NAME(glNormal3hNV) +#define glNormal3hvNV GLH_EXT_NAME(glNormal3hvNV) +#define glColor3hNV GLH_EXT_NAME(glColor3hNV) +#define glColor3hvNV GLH_EXT_NAME(glColor3hvNV) +#define glColor4hNV GLH_EXT_NAME(glColor4hNV) +#define glColor4hvNV GLH_EXT_NAME(glColor4hvNV) +#define glTexCoord1hNV GLH_EXT_NAME(glTexCoord1hNV) +#define glTexCoord1hvNV GLH_EXT_NAME(glTexCoord1hvNV) +#define glTexCoord2hNV GLH_EXT_NAME(glTexCoord2hNV) +#define glTexCoord2hvNV GLH_EXT_NAME(glTexCoord2hvNV) +#define glTexCoord3hNV GLH_EXT_NAME(glTexCoord3hNV) +#define glTexCoord3hvNV GLH_EXT_NAME(glTexCoord3hvNV) +#define glTexCoord4hNV GLH_EXT_NAME(glTexCoord4hNV) +#define glTexCoord4hvNV GLH_EXT_NAME(glTexCoord4hvNV) +#define glMultiTexCoord1hNV GLH_EXT_NAME(glMultiTexCoord1hNV) +#define glMultiTexCoord1hvNV GLH_EXT_NAME(glMultiTexCoord1hvNV) +#define glMultiTexCoord2hNV GLH_EXT_NAME(glMultiTexCoord2hNV) +#define glMultiTexCoord2hvNV GLH_EXT_NAME(glMultiTexCoord2hvNV) +#define glMultiTexCoord3hNV GLH_EXT_NAME(glMultiTexCoord3hNV) +#define glMultiTexCoord3hvNV GLH_EXT_NAME(glMultiTexCoord3hvNV) +#define glMultiTexCoord4hNV GLH_EXT_NAME(glMultiTexCoord4hNV) +#define glMultiTexCoord4hvNV GLH_EXT_NAME(glMultiTexCoord4hvNV) +#define glFogCoordhNV GLH_EXT_NAME(glFogCoordhNV) +#define glFogCoordhvNV GLH_EXT_NAME(glFogCoordhvNV) +#define glSecondaryColor3hNV GLH_EXT_NAME(glSecondaryColor3hNV) +#define glSecondaryColor3hvNV GLH_EXT_NAME(glSecondaryColor3hvNV) +#define glVertexAttrib1hNV GLH_EXT_NAME(glVertexAttrib1hNV) +#define glVertexAttrib1hvNV GLH_EXT_NAME(glVertexAttrib1hvNV) +#define glVertexAttrib2hNV GLH_EXT_NAME(glVertexAttrib2hNV) +#define glVertexAttrib2hvNV GLH_EXT_NAME(glVertexAttrib2hvNV) +#define glVertexAttrib3hNV GLH_EXT_NAME(glVertexAttrib3hNV) +#define glVertexAttrib3hvNV GLH_EXT_NAME(glVertexAttrib3hvNV) +#define glVertexAttrib4hNV GLH_EXT_NAME(glVertexAttrib4hNV) +#define glVertexAttrib4hvNV GLH_EXT_NAME(glVertexAttrib4hvNV) +#define glVertexAttribs1hvNV GLH_EXT_NAME(glVertexAttribs1hvNV) +#define glVertexAttribs2hvNV GLH_EXT_NAME(glVertexAttribs2hvNV) +#define glVertexAttribs3hvNV GLH_EXT_NAME(glVertexAttribs3hvNV) +#define glVertexAttribs4hvNV GLH_EXT_NAME(glVertexAttribs4hvNV) +#endif + +#ifdef GL_NV_light_max_exponent +#endif + +#ifdef GL_NV_multisample_filter_hint +#endif + +#ifdef GL_NV_occlusion_query +#define glGenOcclusionQueriesNV GLH_EXT_NAME(glGenOcclusionQueriesNV) +#define glDeleteOcclusionQueriesNV GLH_EXT_NAME(glDeleteOcclusionQueriesNV) +#define glIsOcclusionQueryNV GLH_EXT_NAME(glIsOcclusionQueryNV) +#define glBeginOcclusionQueryNV GLH_EXT_NAME(glBeginOcclusionQueryNV) +#define glEndOcclusionQueryNV GLH_EXT_NAME(glEndOcclusionQueryNV) +#define glGetOcclusionQueryivNV GLH_EXT_NAME(glGetOcclusionQueryivNV) +#define glGetOcclusionQueryuivNV GLH_EXT_NAME(glGetOcclusionQueryuivNV) +#endif + +#ifdef GL_NV_packed_depth_stencil +#endif + +#ifdef GL_NV_pixel_buffer_object +#endif + +#ifdef GL_NV_pixel_data_range +#define glPixelDataRangeNV GLH_EXT_NAME(glPixelDataRangeNV) +#define glFlushPixelDataRangeNV GLH_EXT_NAME(glFlushPixelDataRangeNV) +#endif + +#ifdef GL_NV_point_sprite +#define glPointParameteriNV GLH_EXT_NAME(glPointParameteriNV) +#define glPointParameterivNV GLH_EXT_NAME(glPointParameterivNV) +#endif + +#ifdef GL_NV_primitive_restart +#define glPrimitiveRestartNV GLH_EXT_NAME(glPrimitiveRestartNV) +#define glPrimitiveRestartIndexNV GLH_EXT_NAME(glPrimitiveRestartIndexNV) +#endif + +#ifdef GL_NV_register_combiners +#define glCombinerParameterfvNV GLH_EXT_NAME(glCombinerParameterfvNV) +#define glCombinerParameterfNV GLH_EXT_NAME(glCombinerParameterfNV) +#define glCombinerParameterivNV GLH_EXT_NAME(glCombinerParameterivNV) +#define glCombinerParameteriNV GLH_EXT_NAME(glCombinerParameteriNV) +#define glCombinerInputNV GLH_EXT_NAME(glCombinerInputNV) +#define glCombinerOutputNV GLH_EXT_NAME(glCombinerOutputNV) +#define glFinalCombinerInputNV GLH_EXT_NAME(glFinalCombinerInputNV) +#define glGetCombinerInputParameterfvNV GLH_EXT_NAME(glGetCombinerInputParameterfvNV) +#define glGetCombinerInputParameterivNV GLH_EXT_NAME(glGetCombinerInputParameterivNV) +#define glGetCombinerOutputParameterfvNV GLH_EXT_NAME(glGetCombinerOutputParameterfvNV) +#define glGetCombinerOutputParameterivNV GLH_EXT_NAME(glGetCombinerOutputParameterivNV) +#define glGetFinalCombinerInputParameterfvNV GLH_EXT_NAME(glGetFinalCombinerInputParameterfvNV) +#define glGetFinalCombinerInputParameterivNV GLH_EXT_NAME(glGetFinalCombinerInputParameterivNV) +#endif + +#ifdef GL_NV_register_combiners2 +#define glCombinerStageParameterfvNV GLH_EXT_NAME(glCombinerStageParameterfvNV) +#define glGetCombinerStageParameterfvNV GLH_EXT_NAME(glGetCombinerStageParameterfvNV) +#endif + +#ifdef GL_NV_stencil_two_side +#define glActiveStencilFaceNV GLH_EXT_NAME(glActiveStencilFaceNV) +#endif + +#ifdef GL_NV_texgen_reflection +#endif + +#ifdef GL_NV_texture_compression_vtc +#endif + +#ifdef GL_NV_texture_env_combine4 +#endif + +#ifdef GL_NV_texture_expand_normal +#endif + +#ifdef GL_NV_texture_rectangle +#endif + +#ifdef GL_NV_texture_shader +#endif + +#ifdef GL_NV_texture_shader2 +#endif + +#ifdef GL_NV_texture_shader3 +#endif + +#ifdef GL_NV_vertex_array_range +#define glFlushVertexArrayRangeNV GLH_EXT_NAME(glFlushVertexArrayRangeNV) +#define glVertexArrayRangeNV GLH_EXT_NAME(glVertexArrayRangeNV) +# ifdef _WIN32 +#define wglAllocateMemoryNV GLH_EXT_NAME(wglAllocateMemoryNV) +# endif +# ifdef GLX_VERSION_1_3 +#define glXAllocateMemoryNV GLH_EXT_NAME(glXAllocateMemoryNV) +# endif +# ifdef _WIN32 +#define wglFreeMemoryNV GLH_EXT_NAME(wglFreeMemoryNV) +# endif +# ifdef GLX_VERSION_1_3 +#define glXFreeMemoryNV GLH_EXT_NAME(glXFreeMemoryNV) +# endif +#endif + +#ifdef GL_NV_vertex_array_range2 +#endif + +#ifdef GL_NV_vertex_program +#define glAreProgramsResidentNV GLH_EXT_NAME(glAreProgramsResidentNV) +#define glBindProgramNV GLH_EXT_NAME(glBindProgramNV) +#define glDeleteProgramsNV GLH_EXT_NAME(glDeleteProgramsNV) +#define glExecuteProgramNV GLH_EXT_NAME(glExecuteProgramNV) +#define glGenProgramsNV GLH_EXT_NAME(glGenProgramsNV) +#define glGetProgramParameterdvNV GLH_EXT_NAME(glGetProgramParameterdvNV) +#define glGetProgramParameterfvNV GLH_EXT_NAME(glGetProgramParameterfvNV) +#define glGetProgramivNV GLH_EXT_NAME(glGetProgramivNV) +#define glGetProgramStringNV GLH_EXT_NAME(glGetProgramStringNV) +#define glGetTrackMatrixivNV GLH_EXT_NAME(glGetTrackMatrixivNV) +#define glGetVertexAttribdvNV GLH_EXT_NAME(glGetVertexAttribdvNV) +#define glGetVertexAttribfvNV GLH_EXT_NAME(glGetVertexAttribfvNV) +#define glGetVertexAttribivNV GLH_EXT_NAME(glGetVertexAttribivNV) +#define glGetVertexAttribPointervNV GLH_EXT_NAME(glGetVertexAttribPointervNV) +#define glIsProgramNV GLH_EXT_NAME(glIsProgramNV) +#define glLoadProgramNV GLH_EXT_NAME(glLoadProgramNV) +#define glProgramParameter4dNV GLH_EXT_NAME(glProgramParameter4dNV) +#define glProgramParameter4dvNV GLH_EXT_NAME(glProgramParameter4dvNV) +#define glProgramParameter4fNV GLH_EXT_NAME(glProgramParameter4fNV) +#define glProgramParameter4fvNV GLH_EXT_NAME(glProgramParameter4fvNV) +#define glProgramParameters4dvNV GLH_EXT_NAME(glProgramParameters4dvNV) +#define glProgramParameters4fvNV GLH_EXT_NAME(glProgramParameters4fvNV) +#define glRequestResidentProgramsNV GLH_EXT_NAME(glRequestResidentProgramsNV) +#define glTrackMatrixNV GLH_EXT_NAME(glTrackMatrixNV) +#define glVertexAttribPointerNV GLH_EXT_NAME(glVertexAttribPointerNV) +#define glVertexAttrib1dNV GLH_EXT_NAME(glVertexAttrib1dNV) +#define glVertexAttrib1dvNV GLH_EXT_NAME(glVertexAttrib1dvNV) +#define glVertexAttrib1fNV GLH_EXT_NAME(glVertexAttrib1fNV) +#define glVertexAttrib1fvNV GLH_EXT_NAME(glVertexAttrib1fvNV) +#define glVertexAttrib1sNV GLH_EXT_NAME(glVertexAttrib1sNV) +#define glVertexAttrib1svNV GLH_EXT_NAME(glVertexAttrib1svNV) +#define glVertexAttrib2dNV GLH_EXT_NAME(glVertexAttrib2dNV) +#define glVertexAttrib2dvNV GLH_EXT_NAME(glVertexAttrib2dvNV) +#define glVertexAttrib2fNV GLH_EXT_NAME(glVertexAttrib2fNV) +#define glVertexAttrib2fvNV GLH_EXT_NAME(glVertexAttrib2fvNV) +#define glVertexAttrib2sNV GLH_EXT_NAME(glVertexAttrib2sNV) +#define glVertexAttrib2svNV GLH_EXT_NAME(glVertexAttrib2svNV) +#define glVertexAttrib3dNV GLH_EXT_NAME(glVertexAttrib3dNV) +#define glVertexAttrib3dvNV GLH_EXT_NAME(glVertexAttrib3dvNV) +#define glVertexAttrib3fNV GLH_EXT_NAME(glVertexAttrib3fNV) +#define glVertexAttrib3fvNV GLH_EXT_NAME(glVertexAttrib3fvNV) +#define glVertexAttrib3sNV GLH_EXT_NAME(glVertexAttrib3sNV) +#define glVertexAttrib3svNV GLH_EXT_NAME(glVertexAttrib3svNV) +#define glVertexAttrib4dNV GLH_EXT_NAME(glVertexAttrib4dNV) +#define glVertexAttrib4dvNV GLH_EXT_NAME(glVertexAttrib4dvNV) +#define glVertexAttrib4fNV GLH_EXT_NAME(glVertexAttrib4fNV) +#define glVertexAttrib4fvNV GLH_EXT_NAME(glVertexAttrib4fvNV) +#define glVertexAttrib4sNV GLH_EXT_NAME(glVertexAttrib4sNV) +#define glVertexAttrib4svNV GLH_EXT_NAME(glVertexAttrib4svNV) +#define glVertexAttrib4ubvNV GLH_EXT_NAME(glVertexAttrib4ubvNV) +#define glVertexAttribs1dvNV GLH_EXT_NAME(glVertexAttribs1dvNV) +#define glVertexAttribs1fvNV GLH_EXT_NAME(glVertexAttribs1fvNV) +#define glVertexAttribs1svNV GLH_EXT_NAME(glVertexAttribs1svNV) +#define glVertexAttribs2dvNV GLH_EXT_NAME(glVertexAttribs2dvNV) +#define glVertexAttribs2fvNV GLH_EXT_NAME(glVertexAttribs2fvNV) +#define glVertexAttribs2svNV GLH_EXT_NAME(glVertexAttribs2svNV) +#define glVertexAttribs3dvNV GLH_EXT_NAME(glVertexAttribs3dvNV) +#define glVertexAttribs3fvNV GLH_EXT_NAME(glVertexAttribs3fvNV) +#define glVertexAttribs3svNV GLH_EXT_NAME(glVertexAttribs3svNV) +#define glVertexAttribs4dvNV GLH_EXT_NAME(glVertexAttribs4dvNV) +#define glVertexAttribs4fvNV GLH_EXT_NAME(glVertexAttribs4fvNV) +#define glVertexAttribs4svNV GLH_EXT_NAME(glVertexAttribs4svNV) +#define glVertexAttribs4ubvNV GLH_EXT_NAME(glVertexAttribs4ubvNV) +#endif + +#ifdef GL_NV_vertex_program1_1 +#endif + +#ifdef GL_NV_vertex_program2 +#endif + +#ifdef GL_NV_vertex_program2_option +#endif + +#ifdef GL_NV_vertex_program3 +#endif + +#ifdef GL_SGIS_generate_mipmap +#endif + +#ifdef GL_SGIS_texture_lod +#endif + +#ifdef GL_SGIX_depth_texture +#endif + +#ifdef GL_SGIX_shadow +#endif + +#ifdef GL_WIN_swap_hint +#define glAddSwapHintRectWIN GLH_EXT_NAME(glAddSwapHintRectWIN) +#endif + +#ifdef WGL_ARB_buffer_region +# ifdef _WIN32 +#define wglCreateBufferRegionARB GLH_EXT_NAME(wglCreateBufferRegionARB) +# endif +# ifdef _WIN32 +#define wglDeleteBufferRegionARB GLH_EXT_NAME(wglDeleteBufferRegionARB) +# endif +# ifdef _WIN32 +#define wglSaveBufferRegionARB GLH_EXT_NAME(wglSaveBufferRegionARB) +# endif +# ifdef _WIN32 +#define wglRestoreBufferRegionARB GLH_EXT_NAME(wglRestoreBufferRegionARB) +# endif +#endif + +#ifdef WGL_ARB_extensions_string +# ifdef _WIN32 +#define wglGetExtensionsStringARB GLH_EXT_NAME(wglGetExtensionsStringARB) +# endif +#endif + +#ifdef WGL_ARB_pbuffer +# ifdef _WIN32 +#define wglCreatePbufferARB GLH_EXT_NAME(wglCreatePbufferARB) +# endif +# ifdef _WIN32 +#define wglGetPbufferDCARB GLH_EXT_NAME(wglGetPbufferDCARB) +# endif +# ifdef _WIN32 +#define wglReleasePbufferDCARB GLH_EXT_NAME(wglReleasePbufferDCARB) +# endif +# ifdef _WIN32 +#define wglDestroyPbufferARB GLH_EXT_NAME(wglDestroyPbufferARB) +# endif +# ifdef _WIN32 +#define wglQueryPbufferARB GLH_EXT_NAME(wglQueryPbufferARB) +# endif +#endif + +#ifdef WGL_ARB_pixel_format +# ifdef _WIN32 +#define wglGetPixelFormatAttribivARB GLH_EXT_NAME(wglGetPixelFormatAttribivARB) +# endif +# ifdef _WIN32 +#define wglGetPixelFormatAttribfvARB GLH_EXT_NAME(wglGetPixelFormatAttribfvARB) +# endif +# ifdef _WIN32 +#define wglChoosePixelFormatARB GLH_EXT_NAME(wglChoosePixelFormatARB) +# endif +#endif + +#ifdef WGL_ARB_render_texture +# ifdef _WIN32 +#define wglBindTexImageARB GLH_EXT_NAME(wglBindTexImageARB) +# endif +# ifdef _WIN32 +#define wglReleaseTexImageARB GLH_EXT_NAME(wglReleaseTexImageARB) +# endif +# ifdef _WIN32 +#define wglSetPbufferAttribARB GLH_EXT_NAME(wglSetPbufferAttribARB) +# endif +#endif + +#ifdef WGL_ATI_pixel_format_float +#endif + +#ifdef WGL_EXT_extensions_string +# ifdef _WIN32 +#define wglGetExtensionsStringEXT GLH_EXT_NAME(wglGetExtensionsStringEXT) +# endif +#endif + +#ifdef WGL_EXT_swap_control +# ifdef _WIN32 +#define wglSwapIntervalEXT GLH_EXT_NAME(wglSwapIntervalEXT) +# endif +# ifdef _WIN32 +#define wglGetSwapIntervalEXT GLH_EXT_NAME(wglGetSwapIntervalEXT) +# endif +#endif + +#ifdef WGL_NV_float_buffer +#endif + +#ifdef WGL_NV_render_depth_texture +#endif + +#ifdef WGL_NV_render_texture_rectangle +#endif + +#ifdef GLX_NV_float_buffer +#endif + +#ifdef GL_NVX_conditional_render +#define glBeginConditionalRenderNVX GLH_EXT_NAME(glBeginConditionalRenderNVX) +#define glEndConditionalRenderNVX GLH_EXT_NAME(glEndConditionalRenderNVX) +#endif + +#ifdef GLX_SGIX_pbuffer +# ifdef GLX_VERSION_1_3 +#define glXCreateGLXPbufferSGIX GLH_EXT_NAME(glXCreateGLXPbufferSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXDestroyGLXPbufferSGIX GLH_EXT_NAME(glXDestroyGLXPbufferSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXQueryGLXPbufferSGIX GLH_EXT_NAME(glXQueryGLXPbufferSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXSelectEventSGIX GLH_EXT_NAME(glXSelectEventSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXGetSelectedEventSGIX GLH_EXT_NAME(glXGetSelectedEventSGIX) +# endif +#endif + +#ifdef GLX_SGIX_fbconfig +# ifdef GLX_VERSION_1_3 +#define glXGetFBConfigAttribSGIX GLH_EXT_NAME(glXGetFBConfigAttribSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXChooseFBConfigSGIX GLH_EXT_NAME(glXChooseFBConfigSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXCreateGLXPixmapWithConfigSGIX GLH_EXT_NAME(glXCreateGLXPixmapWithConfigSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXCreateContextWithConfigSGIX GLH_EXT_NAME(glXCreateContextWithConfigSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXGetVisualFromFBConfigSGIX GLH_EXT_NAME(glXGetVisualFromFBConfigSGIX) +# endif +# ifdef GLX_VERSION_1_3 +#define glXGetFBConfigFromVisualSGIX GLH_EXT_NAME(glXGetFBConfigFromVisualSGIX) +# endif +#endif + + +#endif diff --git a/ThirdParty/GL/glh/glh_glut.h b/ThirdParty/GL/glh/glh_glut.h new file mode 100644 index 0000000..def9f6a --- /dev/null +++ b/ThirdParty/GL/glh/glh_glut.h @@ -0,0 +1,861 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_GLUT_H +#define GLH_GLUT_H + +// some helper functions and object to +// make writing simple glut programs even easier! :-) + +#ifdef MACOS +#include +#else +#include +#endif + +#include +#include +#include + +namespace glh +{ + + class glut_interactor + { + public: + glut_interactor() { enabled = true; } + + virtual void display() {} + virtual void idle() {} + virtual void keyboard(unsigned char key, int x, int y) {} + virtual void menu_status(int status, int x, int y) {} + virtual void motion(int x, int y) {} + virtual void mouse(int button, int state, int x, int y) {} + virtual void passive_motion(int x, int y) {} + virtual void reshape(int w, int h) {} + virtual void special(int key, int x, int y) {} + virtual void timer(int value) {} + virtual void visibility(int v) {} + + virtual void enable() { enabled = true; } + virtual void disable() { enabled = false; } + + bool enabled; + }; + + std::list interactors; + bool propagate; + + void glut_display_function() + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->display(); + } + + void glut_idle_function() + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->idle(); + } + + void glut_keyboard_function(unsigned char k, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->keyboard(k, x, y); + } + + void glut_menu_status_function(int status, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->menu_status(status, x, y); + } + + void glut_motion_function(int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->motion(x, y); + } + + void glut_mouse_function(int button, int state, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->mouse(button, state, x, y); + } + + void glut_passive_motion_function(int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->passive_motion(x, y); + } + + void glut_reshape_function(int w, int h) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->reshape(w,h); + } + + void glut_special_function(int k, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->special(k, x, y); + } + + void glut_timer_function(int v) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->timer(v); + } + + void glut_visibility_function(int v) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->visibility(v); + } + + // stop processing the current event + inline void glut_event_processed() + { + propagate = false; + } + + inline void glut_helpers_initialize() + { + glutDisplayFunc(glut_display_function); + glutIdleFunc(0); + glutKeyboardFunc(glut_keyboard_function); + glutMenuStatusFunc(glut_menu_status_function); + glutMotionFunc(glut_motion_function); + glutMouseFunc(glut_mouse_function); + glutPassiveMotionFunc(glut_passive_motion_function); + glutReshapeFunc(glut_reshape_function); + glutSpecialFunc(glut_special_function); + glutVisibilityFunc(glut_visibility_function); + } + + inline void glut_remove_interactor(glut_interactor *gi) + { + std::list::iterator it = + std::find(interactors.begin(), interactors.end(), gi); + if(it != interactors.end()) + interactors.erase(it); + } + + inline void glut_add_interactor(glut_interactor *gi, bool append=true) + { + glut_remove_interactor(gi); + if(append) + interactors.push_back(gi); + else + interactors.push_front(gi); + } + + inline void glut_timer(int msec, int value) + { + glutTimerFunc(msec, glut_timer_function, value); + } + + inline void glut_idle(bool do_idle) + { + glutIdleFunc(do_idle ? glut_idle_function : 0); + } + + class glut_callbacks : public glut_interactor + { + public: + glut_callbacks() : + display_function(0), + idle_function(0), + keyboard_function(0), + menu_status_function(0), + motion_function(0), + mouse_function(0), + passive_motion_function(0), + reshape_function(0), + special_function(0), + timer_function(0), + visibility_function() + {} + + virtual void display() + { if(display_function) display_function(); } + + virtual void idle() + { if(idle_function) idle_function(); } + + virtual void keyboard(unsigned char k, int x, int y) + { if(keyboard_function) keyboard_function(k, x, y); } + + virtual void menu_status(int status, int x, int y) + { if(menu_status_function) menu_status_function(status, x, y); } + + virtual void motion(int x, int y) + { if(motion_function) motion_function(x,y); } + + virtual void mouse(int button, int state, int x, int y) + { if(mouse_function) mouse_function(button, state, x, y); } + + virtual void passive_motion(int x, int y) + { if(passive_motion_function) passive_motion_function(x, y); } + + virtual void reshape(int w, int h) + { if(reshape_function) reshape_function(w, h); } + + virtual void special(int key, int x, int y) + { if(special_function) special_function(key, x, y); } + + virtual void timer(int value) + { if(timer_function) timer_function(value); } + + virtual void visibility(int v) + { if(visibility_function) visibility_function(v); } + + void (* display_function) (); + void (* idle_function) (); + void (* keyboard_function) (unsigned char, int, int); + void (* menu_status_function) (int, int, int); + void (* motion_function) (int, int); + void (* mouse_function) (int, int, int, int); + void (* passive_motion_function) (int, int); + void (* reshape_function) (int, int); + void (* special_function) (int, int, int); + void (* timer_function) (int); + void (* visibility_function) (int); + + }; + + + + class glut_perspective_reshaper : public glut_interactor + { + public: + glut_perspective_reshaper(float infovy = 60.f, float inzNear = .1f, float inzFar = 10.f) + : fovy(infovy), zNear(inzNear), zFar(inzFar), aspect_factor(1) {} + + void reshape(int w, int h) + { + width = w; height = h; + + if(enabled) apply(); + } + + void apply() + { + glViewport(0,0,width,height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + apply_perspective(); + glMatrixMode(GL_MODELVIEW); + } + void apply_perspective() + { + aspect = aspect_factor * float(width)/float(height); + if ( aspect < 1 ) + { + // fovy is a misnomer.. we really mean the fov applies to the + // smaller dimension + float fovx = fovy; + float real_fov = to_degrees(2 * atan(tan(to_radians(fovx/2))/aspect)); + gluPerspective(real_fov, aspect, zNear, zFar); + } + else + gluPerspective(fovy, aspect, zNear, zFar); + } + int width, height; + float fovy, aspect, zNear, zFar; + float aspect_factor; + }; + + // activates/deactivates on a particular mouse button + // and calculates deltas while active + class glut_simple_interactor : public glut_interactor + { + public: + glut_simple_interactor() + { + activate_on = GLUT_LEFT_BUTTON; + active = false; + use_modifiers = true; + modifiers = 0; + width = height = 0; + x0 = y0 = x = y = dx = dy = 0; + } + + virtual void mouse(int button, int state, int X, int Y) + { + if(enabled && button == activate_on && state == GLUT_DOWN && + (! use_modifiers || (modifiers == glutGetModifiers())) ) + { + active = true; + x = x0 = X; + y = y0 = Y; + dx = dy = 0; + } + else if (enabled && button == activate_on && state == GLUT_UP) + { + if(dx == 0 && dy == 0) + update(); + active = false; + dx = dy = 0; + } + } + + virtual void motion(int X, int Y) + { + if(enabled && active) + { + dx = X - x; dy = y - Y; + x = X; y = Y; + update(); + } + } + + void reshape(int w, int h) + { + width = w; height = h; + } + + virtual void apply_transform() = 0; + virtual void apply_inverse_transform() = 0; + virtual matrix4f get_transform() = 0; + virtual matrix4f get_inverse_transform() = 0; + + virtual void update() {} + + int activate_on; + bool use_modifiers; + int modifiers; + bool active; + int x0, y0; + int x, y; + int dx, dy; + int width, height; + }; + + + class glut_pan : public glut_simple_interactor + { + public: + glut_pan() + { + scale = .01f; + invert_increment = false; + parent_rotation = 0; + } + void update() + { + vec3f v(dx, dy, 0); + if(parent_rotation != 0) parent_rotation->mult_vec(v); + + if(invert_increment) + pan -= v * scale; + else + pan += v * scale; + glutPostRedisplay(); + } + + void apply_transform() + { + //cerr << "Applying transform: " << (x - x0) << ", " << (y - y0) << endl; + glTranslatef(pan[0], pan[1], pan[2]); + } + + void apply_inverse_transform() + { + //cerr << "Applying transform: " << (x - x0) << ", " << (y - y0) << endl; + glTranslatef(-pan[0], -pan[1], -pan[2]); + } + + matrix4f get_transform() + { + matrix4f m; + m.make_identity(); + m.set_translate(pan); + return m; + } + + matrix4f get_inverse_transform() + { + matrix4f m; + m.make_identity(); + m.set_translate(-pan); + return m; + } + + + bool invert_increment; + const rotationf * parent_rotation; + vec3f pan; + float scale; + }; + + + class glut_dolly : public glut_simple_interactor + { + public: + glut_dolly() + { + scale = .01f; + invert_increment = false; + parent_rotation = 0; + } + void update() + { + vec3f v(0,0,dy); + if(parent_rotation != 0) parent_rotation->mult_vec(v); + + if(invert_increment) + dolly += v * scale; + else + dolly -= v * scale; + glutPostRedisplay(); + } + + void apply_transform() + { + //cerr << "Applying transform: " << (x - x0) << ", " << (y - y0) << endl; + glTranslatef(dolly[0], dolly[1], dolly[2]); + } + + void apply_inverse_transform() + { + //cerr << "Applying transform: " << (x - x0) << ", " << (y - y0) << endl; + glTranslatef(-dolly[0], -dolly[1], -dolly[2]); + } + + matrix4f get_transform() + { + matrix4f m; + m.make_identity(); + m.set_translate(dolly); + return m; + } + + matrix4f get_inverse_transform() + { + matrix4f m; + m.make_identity(); + m.set_translate(-dolly); + return m; + } + + + bool invert_increment; + const rotationf * parent_rotation; + vec3f dolly; + float scale; + }; + + + class glut_trackball : public glut_simple_interactor + { + public: + glut_trackball() + { + r = rotationf(vec3f(0, 1, 0), 0); + centroid = vec3f(0,0,0); + scale = 1; + invert_increment = false; + parent_rotation = 0; + legacy_mode = false; + } + + void update() + { + if(dx == 0 && dy == 0) + { + incr = rotationf(); + return; + } + + if(legacy_mode) + { + vec3f v(dy, -dx, 0); + float len = v.normalize(); + if(parent_rotation != 0) parent_rotation->mult_vec(v); + //r.mult_dir(vec3f(v), v); + if(invert_increment) + incr.set_value(v, -len * scale * -.01); + else + incr.set_value(v, len * scale * -.01); + } + else + { + float min = width < height ? width : height; + min /= 2.f; + vec3f offset(width/2.f, height/2.f, 0); + vec3f a(x-dx, height - (y+dy), 0); + vec3f b( x, height - y , 0); + a -= offset; + b -= offset; + a /= min; + b /= min; + + a[2] = pow(2.0f, -0.5f * a.length()); + a.normalize(); + b[2] = pow(2.0f, -0.5f * b.length()); + b.normalize(); + + + vec3f axis = a.cross(b); + axis.normalize(); + + float angle = acos(a.dot(b)); + + if(parent_rotation != 0) parent_rotation->mult_vec(axis); + + if(invert_increment) + incr.set_value(axis, -angle * scale); + else + incr.set_value(axis, angle * scale); + + } + + // fixme: shouldn't operator*() preserve 'r' in this case? + if(incr[3] != 0) + r = incr * r; + glutPostRedisplay(); + } + + void increment_rotation() + { + if(active) return; + // fixme: shouldn't operator*() preserve 'r' in this case? + if(incr[3] != 0) + r = incr * r; + glutPostRedisplay(); + } + + void apply_transform() + { + glTranslatef(centroid[0], centroid[1], centroid[2]); + glh_rotate(r); + glTranslatef(-centroid[0], -centroid[1], -centroid[2]); + } + + void apply_inverse_transform() + { + glTranslatef(centroid[0], centroid[1], centroid[2]); + glh_rotate(r.inverse()); + glTranslatef(-centroid[0], -centroid[1], -centroid[2]); + } + + + matrix4f get_transform() + { + matrix4f mt, mr, minvt; + mt.set_translate(centroid); + r.get_value(mr); + minvt.set_translate(-centroid); + return mt * mr * minvt; + } + + matrix4f get_inverse_transform() + { + matrix4f mt, mr, minvt; + mt.set_translate(centroid); + r.inverse().get_value(mr); + minvt.set_translate(-centroid); + return mt * mr * minvt; + } + + bool invert_increment; + const rotationf * parent_rotation; + rotationf r; + vec3f centroid; + float scale; + bool legacy_mode; + rotationf incr; + }; + + + class glut_rotate : public glut_simple_interactor + { + public: + glut_rotate() + { + rotate_x = rotate_y = 0; + scale = 1; + } + void update() + { + rotate_x += dx * scale; + rotate_y += dy * scale; + glutPostRedisplay(); + } + + void apply_transform() + { + glRotatef(rotate_x, 0, 1, 0); + glRotatef(rotate_y, -1, 0, 0); + } + + void apply_inverse_transform() + { + glRotatef(-rotate_y, -1, 0, 0); + glRotatef(-rotate_x, 0, 1, 0); + } + + matrix4f get_transform() + { + rotationf rx(to_radians(rotate_x), 0, 1, 0); + rotationf ry(to_radians(rotate_y), -1, 0, 0); + matrix4f mx, my; + rx.get_value(mx); + ry.get_value(my); + return mx * my; + } + + matrix4f get_inverse_transform() + { + rotationf rx(to_radians(-rotate_x), 0, 1, 0); + rotationf ry(to_radians(-rotate_y), -1, 0, 0); + matrix4f mx, my; + rx.get_value(mx); + ry.get_value(my); + return my * mx; + } + + float rotate_x, rotate_y, scale; + }; + + class glut_mouse_to_keyboard : public glut_simple_interactor + { + public: + glut_mouse_to_keyboard() + { + keyboard_function = 0; + pos_dx_key = neg_dx_key = pos_dy_key = neg_dy_key = 0; + } + void apply_transform() {} + + void apply_inverse_transform() {} + + matrix4f get_transform() {return matrix4f();} + + matrix4f get_inverse_transform() {return matrix4f();} + + void update() + { + if(!keyboard_function) return; + if(dx > 0) + keyboard_function(pos_dx_key, x, y); + else if(dx < 0) + keyboard_function(neg_dx_key, x, y); + if(dy > 0) + keyboard_function(pos_dy_key, x, y); + else if(dy < 0) + keyboard_function(neg_dy_key, x, y); + } + unsigned char pos_dx_key, neg_dx_key, pos_dy_key, neg_dy_key; + void (*keyboard_function)(unsigned char, int, int); + }; + + inline void glut_exit_on_escape(unsigned char k, int x = 0, int y = 0) + { if(k==27) exit(0); } + + struct glut_simple_mouse_interactor : public glut_interactor + { + + public: + glut_simple_mouse_interactor(int num_buttons_to_use=3) + { + configure_buttons(num_buttons_to_use); + camera_mode = false; + } + + void enable() + { + trackball.enable(); + pan.enable(); + dolly.enable(); + } + + void disable() + { + trackball.disable(); + pan.disable(); + dolly.disable(); + } + + void set_camera_mode(bool cam) + { + camera_mode = cam; + if(camera_mode) + { + trackball.invert_increment = true; + pan.invert_increment = true; + dolly.invert_increment = true; + pan.parent_rotation = & trackball.r; + dolly.parent_rotation = & trackball.r; + } + else + { + trackball.invert_increment = false; + pan.invert_increment = false; + dolly.invert_increment = false; + if(pan.parent_rotation == &trackball.r) pan.parent_rotation = 0; + if(dolly.parent_rotation == &trackball.r) dolly.parent_rotation = 0; + } + } + void configure_buttons(int num_buttons_to_use = 3) + { + switch(num_buttons_to_use) + { + case 1: + trackball.activate_on = GLUT_LEFT_BUTTON; + trackball.modifiers = 0; + + pan.activate_on = GLUT_LEFT_BUTTON; + pan.modifiers = GLUT_ACTIVE_SHIFT; + + + dolly.activate_on = GLUT_LEFT_BUTTON; + dolly.modifiers = GLUT_ACTIVE_CTRL; + + break; + + case 2: + trackball.activate_on = GLUT_LEFT_BUTTON; + trackball.modifiers = 0; + + pan.activate_on = GLUT_MIDDLE_BUTTON; + pan.modifiers = 0; + + + dolly.activate_on = GLUT_LEFT_BUTTON; + dolly.modifiers = GLUT_ACTIVE_CTRL; + + break; + + case 3: + default: + trackball.activate_on = GLUT_LEFT_BUTTON; + trackball.modifiers = 0; + + pan.activate_on = GLUT_MIDDLE_BUTTON; + pan.modifiers = 0; + + dolly.activate_on = GLUT_RIGHT_BUTTON; + dolly.modifiers = 0; + + break; + } + } + + virtual void motion(int x, int y) + { + + trackball.motion(x,y); + + pan.motion(x,y); + + dolly.motion(x,y); + } + virtual void mouse(int button, int state, int x, int y) + { + trackball.mouse(button, state, x, y); + pan.mouse(button, state, x, y); + dolly.mouse(button, state, x, y); + } + + virtual void reshape(int x, int y) + { + trackball.reshape(x,y); + pan.reshape(x,y); + dolly.reshape(x,y); + } + + void apply_transform() + { + pan.apply_transform(); + dolly.apply_transform(); + trackball.apply_transform(); + } + + void apply_inverse_transform() + { + trackball.apply_inverse_transform(); + dolly.apply_inverse_transform(); + pan.apply_inverse_transform(); + } + + matrix4f get_transform() + { + return ( pan.get_transform() * + dolly.get_transform() * + trackball.get_transform() ); + } + + matrix4f get_inverse_transform() + { + return ( trackball.get_inverse_transform() * + dolly.get_inverse_transform() * + pan.get_inverse_transform() ); + } + + void set_parent_rotation(rotationf *rp) + { + trackball.parent_rotation = rp; + dolly.parent_rotation = rp; + pan.parent_rotation = rp; + } + + bool camera_mode; + glut_trackball trackball; + glut_pan pan; + glut_dolly dolly; + }; + +} + +#endif diff --git a/ThirdParty/GL/glh/glh_glut2.h b/ThirdParty/GL/glh/glh_glut2.h new file mode 100644 index 0000000..908a0fc --- /dev/null +++ b/ThirdParty/GL/glh/glh_glut2.h @@ -0,0 +1,667 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_GLUT_H +#define GLH_GLUT_H + +// some helper functions and objects to +// make writing simple glut programs even easier! :-) +#ifndef _WIN32 +#include +#endif +#include +#include + +#ifdef MACOS +#include +#else +#include +#endif + +#include +#include + +namespace glh +{ + + class glut_interactor + { + public: + glut_interactor() { enabled = true; } + + virtual void display() {} + virtual void idle() {} + virtual void keyboard(unsigned char key, int x, int y) {} + virtual void menu_status(int status, int x, int y) {} + virtual void motion(int x, int y) {} + virtual void mouse(int button, int state, int x, int y) {} + virtual void passive_motion(int x, int y) {} + virtual void reshape(int w, int h) {} + virtual void special(int key, int x, int y) {} + virtual void timer(int value) {} + virtual void visibility(int v) {} + + virtual void enable() { enabled = true; } + virtual void disable() { enabled = false; } + + bool enabled; + }; + + std::list interactors; + bool propagate; + + void glut_display_function() + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->display(); + } + + void glut_idle_function() + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->idle(); + } + + void glut_keyboard_function(unsigned char k, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->keyboard(k, x, y); + } + + void glut_menu_status_function(int status, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->menu_status(status, x, y); + } + + void glut_motion_function(int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->motion(x, y); + } + + void glut_mouse_function(int button, int state, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->mouse(button, state, x, y); + } + + void glut_passive_motion_function(int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->passive_motion(x, y); + } + + void glut_reshape_function(int w, int h) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->reshape(w,h); + } + + void glut_special_function(int k, int x, int y) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->special(k, x, y); + } + + void glut_timer_function(int v) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->timer(v); + } + + void glut_visibility_function(int v) + { + propagate = true; + for(std::list::iterator it=interactors.begin(); it != interactors.end() && propagate; it++) + (*it)->visibility(v); + } + + // stop processing the current event + inline void glut_event_processed() + { + propagate = false; + } + + inline void glut_helpers_initialize() + { + glutDisplayFunc(glut_display_function); + glutIdleFunc(0); + glutKeyboardFunc(glut_keyboard_function); + glutMenuStatusFunc(glut_menu_status_function); + glutMotionFunc(glut_motion_function); + glutMouseFunc(glut_mouse_function); + glutPassiveMotionFunc(glut_passive_motion_function); + glutReshapeFunc(glut_reshape_function); + glutSpecialFunc(glut_special_function); + glutVisibilityFunc(glut_visibility_function); + } + + inline void glut_remove_interactor(glut_interactor *gi) + { + if (interactors.empty()) + return; + std::list::iterator it = + std::find(interactors.begin(), interactors.end(), gi); + if(it != interactors.end()) + interactors.erase(it); + } + + inline void glut_add_interactor(glut_interactor *gi, bool append=true) + { + glut_remove_interactor(gi); + if(append) + interactors.push_back(gi); + else + interactors.push_front(gi); + } + + inline void glut_timer(int msec, int value) + { + glutTimerFunc(msec, glut_timer_function, value); + } + + inline void glut_idle(bool do_idle) + { + glutIdleFunc(do_idle ? glut_idle_function : 0); + } + + class glut_callbacks : public glut_interactor + { + public: + glut_callbacks() : + display_function(0), + idle_function(0), + keyboard_function(0), + menu_status_function(0), + motion_function(0), + mouse_function(0), + passive_motion_function(0), + reshape_function(0), + special_function(0), + timer_function(0), + visibility_function() + {} + + virtual void display() + { if(display_function) display_function(); } + + virtual void idle() + { if(idle_function) idle_function(); } + + virtual void keyboard(unsigned char k, int x, int y) + { if(keyboard_function) keyboard_function(k, x, y); } + + virtual void menu_status(int status, int x, int y) + { if(menu_status_function) menu_status_function(status, x, y); } + + virtual void motion(int x, int y) + { if(motion_function) motion_function(x,y); } + + virtual void mouse(int button, int state, int x, int y) + { if(mouse_function) mouse_function(button, state, x, y); } + + virtual void passive_motion(int x, int y) + { if(passive_motion_function) passive_motion_function(x, y); } + + virtual void reshape(int w, int h) + { if(reshape_function) reshape_function(w, h); } + + virtual void special(int key, int x, int y) + { if(special_function) special_function(key, x, y); } + + virtual void timer(int value) + { if(timer_function) timer_function(value); } + + virtual void visibility(int v) + { if(visibility_function) visibility_function(v); } + + void (* display_function) (); + void (* idle_function) (); + void (* keyboard_function) (unsigned char, int, int); + void (* menu_status_function) (int, int, int); + void (* motion_function) (int, int); + void (* mouse_function) (int, int, int, int); + void (* passive_motion_function) (int, int); + void (* reshape_function) (int, int); + void (* special_function) (int, int, int); + void (* timer_function) (int); + void (* visibility_function) (int); + + }; + + + + class glut_perspective_reshaper : public glut_interactor + { + public: + glut_perspective_reshaper(float infovy = 60.f, float inzNear = .1f, float inzFar = 10.f) + : fovy(infovy), zNear(inzNear), zFar(inzFar), aspect_factor(1) {} + + void reshape(int w, int h) + { + width = w; height = h; + + if(enabled) apply(); + } + + void apply() + { + glViewport(0,0,width,height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + apply_projection(); + glMatrixMode(GL_MODELVIEW); + } + + matrix4f get_projection() + { + aspect = aspect_factor * float(width)/float(height); + if ( aspect < 1 ) + { + // fovy is a misnomer.. we really mean the fov applies to the + // smaller dimension + float fovx = fovy; + float real_fov = to_degrees(2 * atan(tan(to_radians(fovx/2))/aspect)); + return perspective(real_fov, aspect, zNear, zFar); + } + else + return perspective(fovy, aspect, zNear, zFar); + } + + void apply_projection() + { + glMultMatrixf(get_projection().m); + } + + matrix4f get_projection_inverse() + { + aspect = aspect_factor * float(width)/float(height); + if ( aspect < 1 ) + { + // fovy is a misnomer.. we really mean the fov applies to the + // smaller dimension + float fovx = fovy; + float real_fov = to_degrees(2 * atan(tan(to_radians(fovx/2))/aspect)); + return perspective_inverse(real_fov, aspect, zNear, zFar); + } + else + return perspective_inverse(fovy, aspect, zNear, zFar); + } + + void apply_projection_inverse() + { + glMultMatrixf(get_projection_inverse().m); + } + + int width, height; + float fovy, aspect, zNear, zFar; + float aspect_factor; + }; + + + // activates/deactivates on particular mouse button/modifier combinations + // and calculates deltas while active + class glut_simple_interactor : public glut_interactor + { + public: + glut_simple_interactor() + { + num_modes = 0; + active = false; + width = height = 0; + x0 = y0 = x = y = dx = dy = 0; + } + + virtual void mouse(int button, int state, int X, int Y) + { + for(int i=0; i < num_modes; i++) + { + if(enabled && button == cond[i].activate_on && state == GLUT_DOWN && + (! cond[i].use_modifiers || (cond[i].modifiers == glutGetModifiers())) ) + { + mode = i; + active = true; + x = x0 = X; + y = y0 = height - Y; + dx = dy = 0; + break; + } + else if (enabled && button == cond[i].activate_on && state == GLUT_UP) + { + if(dx == 0 && dy == 0) + update(); + active = false; + dx = dy = 0; + mode = -1; + break; + } + } + } + + virtual void motion(int X, int Y) + { + if(enabled && active) + { + dx = X - x; dy = (height - Y) - y; + x = X; y = height - Y; + update(); + } + } + + void reshape(int w, int h) + { + width = w; height = h; + } + + virtual void apply_transform() = 0; + virtual void apply_inverse_transform() = 0; + virtual matrix4f get_transform() = 0; + virtual matrix4f get_inverse_transform() = 0; + + virtual void update() {} + + struct activate_condition + { + activate_condition() + { + activate_on = GLUT_LEFT_BUTTON; + use_modifiers = true; + modifiers = 0; + } + int activate_on; + bool use_modifiers; + int modifiers; + }; + + activate_condition cond[2]; + bool active; + int x0, y0; + int x, y; + int dx, dy; + int width, height; + int num_modes; + int mode; + }; + + + class glut_translator : public translator, public glut_simple_interactor + { + public: + + void update() + { + if(mode == 0) + pan(dx, dy); + else if(mode == 1) + dolly(-dy); + glutPostRedisplay(); + } + + void apply_transform() + { + //cerr << "Applying transform: " << (x - x0) << ", " << (y - y0) << endl; + glTranslatef(t[0], t[1], t[2]); + } + + void apply_inverse_transform() + { + //cerr << "Applying transform: " << (x - x0) << ", " << (y - y0) << endl; + glTranslatef(-t[0], -t[1], -t[2]); + } + + matrix4f get_transform() + { + return translator::get_transform(); + } + + matrix4f get_inverse_transform() + { + return translator::get_inverse_transform(); + } + + }; + + + class glut_trackball : public glut_simple_interactor, public trackball + { + public: + + void update() + { + radius = width < height ? width : height; + radius /= 2.f; + offset = vec3f(width/2.f, height/2.f, 0); + rotate(x-dx, y-dy, x, y); + glutPostRedisplay(); + } + + void apply_transform() + { + glTranslatef(centroid[0], centroid[1], centroid[2]); + glh_rotate(r); + glTranslatef(-centroid[0], -centroid[1], -centroid[2]); + } + + void apply_inverse_transform() + { + glTranslatef(centroid[0], centroid[1], centroid[2]); + glh_rotate(r.inverse()); + glTranslatef(-centroid[0], -centroid[1], -centroid[2]); + } + + matrix4f get_transform() + { + return trackball::get_transform(); + } + + matrix4f get_inverse_transform() + { + return trackball::get_inverse_transform(); + } + + }; + + inline void glut_exit_on_escape(unsigned char k, int x = 0, int y = 0) + { if(k==27) exit(0); } + + struct glut_simple_mouse_interactor : public glut_interactor + { + + public: + glut_simple_mouse_interactor(int num_buttons_to_use=3) + { + configure_buttons(num_buttons_to_use); + camera_mode = false; + } + + void enable() + { + trackball.enable(); + translator.enable(); + } + + void disable() + { + trackball.disable(); + translator.disable(); + } + + void set_camera_mode(bool cam) + { + camera_mode = cam; + if(camera_mode) + { + trackball.invert_increment = true; + translator.invert_increment = true; + translator.parent_rotation = & trackball.r; + } + else + { + trackball.invert_increment = false; + translator.invert_increment = false; + if(translator.parent_rotation == &trackball.r) translator.parent_rotation = 0; + } + } + void configure_buttons(int num_buttons_to_use = 3) + { + switch(num_buttons_to_use) + { + case 1: + + trackball.num_modes = 1; + trackball.cond[0].activate_on = GLUT_LEFT_BUTTON; + trackball.cond[0].modifiers = 0; + trackball.cond[0].use_modifiers = true; + + translator.num_modes = 2; + translator.cond[0].activate_on = GLUT_LEFT_BUTTON; + translator.cond[0].modifiers = GLUT_ACTIVE_SHIFT; + translator.cond[0].use_modifiers = true; + translator.cond[1].activate_on = GLUT_LEFT_BUTTON; + translator.cond[1].modifiers = GLUT_ACTIVE_CTRL; + translator.cond[1].use_modifiers = true; + break; + + case 2: + + trackball.num_modes = 1; + trackball.cond[0].activate_on = GLUT_LEFT_BUTTON; + trackball.cond[0].modifiers = 0; + trackball.cond[0].use_modifiers = true; + + translator.num_modes = 2; + translator.cond[0].activate_on = GLUT_MIDDLE_BUTTON; + translator.cond[0].modifiers = 0; + translator.cond[0].use_modifiers = true; + translator.cond[1].activate_on = GLUT_LEFT_BUTTON; + translator.cond[1].modifiers = GLUT_ACTIVE_CTRL; + translator.cond[1].use_modifiers = true; + + break; + + case 3: + default: + + trackball.num_modes = 1; + trackball.cond[0].activate_on = GLUT_LEFT_BUTTON; + trackball.cond[0].modifiers = 0; + trackball.cond[0].use_modifiers = true; + + translator.num_modes = 2; + translator.cond[0].activate_on = GLUT_MIDDLE_BUTTON; + translator.cond[0].modifiers = 0; + translator.cond[0].use_modifiers = true; + translator.cond[1].activate_on = GLUT_RIGHT_BUTTON; + translator.cond[1].modifiers = 0; + translator.cond[1].use_modifiers = true; + + break; + } + } + + virtual void motion(int x, int y) + { + trackball.motion(x,y); + translator.motion(x,y); + } + + virtual void mouse(int button, int state, int x, int y) + { + trackball.mouse(button, state, x, y); + translator.mouse(button, state, x, y); + } + + virtual void reshape(int x, int y) + { + trackball.reshape(x,y); + translator.reshape(x,y); + } + + void apply_transform() + { + translator.apply_transform(); + trackball.apply_transform(); + } + + void apply_inverse_transform() + { + trackball.apply_inverse_transform(); + translator.apply_inverse_transform(); + } + + matrix4f get_transform() + { + return ( translator.get_transform() * + trackball.get_transform() ); + } + + matrix4f get_inverse_transform() + { + return ( trackball.get_inverse_transform() * + translator.get_inverse_transform() ); + } + + void set_parent_rotation(rotationf *rp) + { + trackball.parent_rotation = rp; + translator.parent_rotation = rp; + } + + bool camera_mode; + glut_trackball trackball; + glut_translator translator; + }; + +} + +#endif diff --git a/ThirdParty/GL/glh/glh_glut_callfunc.h b/ThirdParty/GL/glh/glh_glut_callfunc.h new file mode 100644 index 0000000..52fd7c1 --- /dev/null +++ b/ThirdParty/GL/glh/glh_glut_callfunc.h @@ -0,0 +1,85 @@ +/* +glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_GLUT_CALLFUNC_H +#define GLH_GLUT_CALLFUNC_H + +#include + +#ifdef _WIN32 +#define GLH_FUNC extern "C" __declspec(dllexport) +#else +#define GLH_FUNC +#endif + + +namespace glh +{ + + struct library_handle + { +#ifdef _WIN32 + bool init(const char * name) + { + lib = GetModuleHandle(name); + if(lib) return true; + std::string n(name); + n += ".exe"; + lib = GetModuleHandle(n.c_str()); + + return lib != 0; + } + void call_func(const char k, int x, int y) + { + std::string entry_name("key__"); + entry_name += k; + void (*entry)(int, int) = (void (*)(int, int))GetProcAddress(lib, entry_name.c_str()); + if(entry) + (*entry)(x, y); + } + HMODULE lib; +#endif + }; + +} + +#endif diff --git a/ThirdParty/GL/glh/glh_glut_replay.h b/ThirdParty/GL/glh/glh_glut_replay.h new file mode 100644 index 0000000..0d9d1ea --- /dev/null +++ b/ThirdParty/GL/glh/glh_glut_replay.h @@ -0,0 +1,292 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_GLUT_REPLAY_H +#define GLH_GLUT_REPLAY_H + +// a facility for recording and playing back glut events + +#include +#include + +namespace glh +{ + + + struct glut_event + { + enum event_type + { + DISPLAY, IDLE, KEYBOARD, MENU_STATUS, MOTION, MOUSE, + PASSIVE_MOTION, RESHAPE, SPECIAL, TIMER, VISIBILITY + }; + glut_event(event_type t) : type(t) {} + virtual ~glut_event() {} + virtual void dispatch() = 0; + const event_type type; + }; + + struct glut_display_event : public glut_event + { + glut_display_event() : glut_event(DISPLAY) {} + virtual void dispatch() { glut_display_function(); } + }; + + struct glut_idle_event : public glut_event + { + glut_idle_event() : glut_event(IDLE) {} + virtual void dispatch() { glut_idle_function(); } + }; + + struct glut_keyboard_event : public glut_event + { + glut_keyboard_event(unsigned char key, int xpos, int ypos) + : glut_event(KEYBOARD), k(key), x(xpos), y(ypos) + {} + virtual void dispatch() { glut_keyboard_function(k,x,y); } + unsigned char k; + int x, y; + }; + + struct glut_menu_status_event : public glut_event + { + glut_menu_status_event(int status, int xpos, int ypos) + : glut_event(MENU_STATUS), s(status), x(xpos), y(ypos) + {} + virtual void dispatch() { glut_menu_status_function(s,x,y); } + int s, x, y; + }; + + struct glut_motion_event : public glut_event + { + glut_motion_event(int xpos, int ypos) + : glut_event(MOTION), x(xpos), y(ypos) + {} + virtual void dispatch() { glut_motion_function(x,y); } + int x, y; + }; + + struct glut_mouse_event : public glut_event + { + glut_mouse_event(int button, int state, int xpos, int ypos) + : glut_event(MOUSE), b(button), s(state), x(xpos), y(ypos) + {} + virtual void dispatch() { glut_mouse_function(b,s,x,y); } + int b, s, x, y; + }; + + struct glut_passive_motion_event : public glut_event + { + glut_passive_motion_event(int xpos, int ypos) + : glut_event(PASSIVE_MOTION), x(xpos), y(ypos) + {} + virtual void dispatch() { glut_passive_motion_function(x,y); } + int x, y; + }; + + struct glut_reshape_event : public glut_event + { + glut_reshape_event(int width, int height) + : glut_event(RESHAPE), w(width), h(height) + {} + virtual void dispatch() { glut_reshape_function(w,h); } + int w, h; + }; + + struct glut_special_event : public glut_event + { + glut_special_event(int key, int xpos, int ypos) + : glut_event(SPECIAL), k(key), x(xpos), y(ypos) + {} + virtual void dispatch() { glut_special_function(k,x,y); } + int k, x, y; + }; + + struct glut_timer_event : public glut_event + { + glut_timer_event(int value) + : glut_event(TIMER), v(value) + {} + virtual void dispatch() { glut_timer_function(v); } + int v; + }; + + struct glut_visibility_event : public glut_event + { + glut_visibility_event(int visibility) + : glut_event(VISIBILITY), v(visibility) + {} + virtual void dispatch() { glut_visibility_function(v); } + int v; + }; + + + + struct glut_replay : public glut_interactor + { + enum recorder_mode + { + RECORD, PLAY, STOP + }; + + glut_replay() + { + it = event_list.end(); + mode = STOP; + paused = false; + } + + virtual ~glut_replay() + { erase(); } + + virtual void display() + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_display_event()); + } + virtual void idle() + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_idle_event()); + } + virtual void keyboard(unsigned char key, int x, int y) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_keyboard_event(key,x,y)); + } + virtual void menu_status(int status, int x, int y) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_menu_status_event(status,x,y)); + } + virtual void motion(int x, int y) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_motion_event(x,y)); + } + virtual void mouse(int button, int state, int x, int y) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_mouse_event(button,state,x,y)); + } + virtual void passive_motion(int x, int y) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_passive_motion_event(x,y)); + } + virtual void reshape(int w, int h) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_reshape_event(w,h)); + } + virtual void special(int key, int x, int y) + { + if(enabled) + { + if (key == GLUT_KEY_F6) + { mode = RECORD; glut_event_processed(); erase(); } + else if(key == GLUT_KEY_F7) + { mode = PLAY; glut_event_processed(); glutPostRedisplay(); } + else if(key == GLUT_KEY_F8) + { mode = STOP; glut_event_processed(); } + else if(key == GLUT_KEY_F9) + { paused = !paused; glut_event_processed(); glutPostRedisplay(); } + else if(RECORD == mode) + { event_list.push_back(new glut_special_event(key,x,y)); } + } + } + virtual void timer(int value) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_timer_event(value)); + } + virtual void visibility(int v) + { + if(enabled && RECORD == mode && ! paused) + event_list.push_back(new glut_visibility_event(v)); + } + + + // other methods + + bool playing() { return mode == PLAY; } + + void dispatch_accumulated_events() + { + if(mode == PLAY && ! paused) + { + while(it != event_list.end() && (*it)->type != glut_event::DISPLAY) + { + (*it)->dispatch(); + it++; + } + if(it == event_list.end()) + { + mode = STOP; + it = event_list.begin(); + } + else if ((*it)->type == glut_event::DISPLAY) + { + it++; + } + } + } + + void erase() + { + while(event_list.begin() != event_list.end()) + { + glut_event * e = event_list.back(); + event_list.pop_back(); + delete e; + } + } + + std::list event_list; + std::list::iterator it; + recorder_mode mode; + bool paused; + }; + +} + +#endif diff --git a/ThirdParty/GL/glh/glh_glut_text.h b/ThirdParty/GL/glh/glh_glut_text.h new file mode 100644 index 0000000..aba823e --- /dev/null +++ b/ThirdParty/GL/glh/glh_glut_text.h @@ -0,0 +1,195 @@ +/* +glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_GLUT_TEXT_H +#define GLH_GLUT_TEXT_H + +#include +#include + +#ifdef MACOS +#include +#else +#include +#endif + +namespace glh +{ + + struct glut_stroke_roman : font + { + glut_stroke_roman() : initialized(false) {} + + virtual ~glut_stroke_roman() { } + + struct glyph + { + float width; + display_list dl; + }; + + + // get font metrics + virtual float get_ascent() + { return 119.05f; } + virtual float get_descent() + { return 33.33f; } + virtual float get_width(int i) + { + if(32 <= i && i <= 127) + { + if(! initialized) init(); + return glyphs[i].width; + } + return 0; + } + + // draw + virtual void render(int i) + { + if(32 <= i && i <= 127) + { + if(! initialized) init(); + glyphs[i].dl.call_list(); + } + } + + virtual void initialize() { if(! initialized) init(); } + + bool initialized; + glyph glyphs[128]; + + private: + + void init() + { + initialized = true; + GLfloat m[16]; + glColorMask(0,0,0,0); + glDepthMask(0); + glStencilMask(0); + for(int i=32; i < 128; i++) + { + glPushMatrix(); + glLoadIdentity(); + + // build display list + glyphs[i].dl.new_list(GL_COMPILE_AND_EXECUTE); + glutStrokeCharacter(GLUT_STROKE_ROMAN, i); + glyphs[i].dl.end_list(); + // get *float* character width (glut only returns integer width) + glGetFloatv(GL_MODELVIEW_MATRIX, m); + glyphs[i].width = m[12]; + + glPopMatrix(); + } + glColorMask(1,1,1,1); + glDepthMask(1); + glStencilMask(1); + } + + }; + + + struct glut_stroke_mono_roman : font + { + glut_stroke_mono_roman() : initialized(false) {} + + virtual ~glut_stroke_mono_roman() { } + + struct glyph + { + display_list dl; + }; + + + // get font metrics + virtual float get_ascent() + { return 119.05f; } + virtual float get_descent() + { return 33.33f; } + virtual float get_width(int i) + { + if(32 <= i && i <= 127) + { + if(! initialized) init(); + return 104.76; + } + return 0; + } + + // draw + virtual void render(int i) + { + if(32 <= i && i <= 127) + { + if(! initialized) init(); + glyphs[i].dl.call_list(); + } + } + + virtual void initialize() { if(! initialized) init(); } + + bool initialized; + glyph glyphs[128]; + + private: + + void init() + { + initialized = true; + for(int i=32; i < 128; i++) + { + // build display list + glyphs[i].dl.new_list(GL_COMPILE); + glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, i); + glyphs[i].dl.end_list(); + } + } + + }; + + + +} + +#endif diff --git a/ThirdParty/GL/glh/glh_interactors.h b/ThirdParty/GL/glh/glh_interactors.h new file mode 100644 index 0000000..03e72a3 --- /dev/null +++ b/ThirdParty/GL/glh/glh_interactors.h @@ -0,0 +1,213 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2001 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_INTERACTORS_H +#define GLH_INTERACTORS_H + +#include + +namespace glh +{ + + class translator + { + public: + translator() + { + scale = .01f; + invert_increment = false; + parent_rotation = 0; + } + + void pan (int dx, int dy) { update(dx, dy, 0); } + void dolly(int dz) { update( 0, 0, dz); } + + void update(int dx, int dy, int dz) + { + vec3f v(dx, dy, dz); + + // apply parent rotation + if(parent_rotation != 0) + parent_rotation->mult_vec(v); + + if(invert_increment) + t -= v * scale; + else + t += v * scale; + } + + + matrix4f get_transform() + { + matrix4f m; + m.set_translate(t); + return m; + } + + matrix4f get_inverse_transform() + { + matrix4f m; + m.set_translate(-t); + return m; + } + + bool invert_increment; + const rotationf * parent_rotation; + vec3f t; + float scale; + }; + + + + class trackball + { + public: + trackball() + { + r = rotationf(vec3f(0, 1, 0), 0); + centroid = vec3f(0,0,0); + scale = -.01f; + invert_increment = false; + parent_rotation = 0; + legacy_mode = false; + } + + void rotate(int x0, int y0, int x1, int y1) + { update(x0, y0, x1, y1); } + + void update(int x0, int y0, int x1, int y1) + { + int dx = x1 - x0; + int dy = y1 - y0; + if(dx == 0 && dy == 0) + { + incr = rotationf(); + return; + } + + if(legacy_mode) + { + vec3f v(dy, -dx, 0); + float len = v.normalize(); + if(parent_rotation != 0) + parent_rotation->mult_vec(v); + //r.mult_dir(vec3f(v), v); + if(invert_increment) + incr.set_value(v, -len * scale); + else + incr.set_value(v, len * scale); + } + else + { + vec3f a(x0, y0, 0); + vec3f b(x1, y1, 0); + a -= offset; + b -= offset; + a /= radius; + b /= radius; + + float tmpscale = 1; + a[2] = pow(2.0f, -0.5f * a.length()); + a.normalize(); + b[2] = pow(2.0f, -0.5f * b.length()); + b.normalize(); + + + + vec3f axis = a.cross(b); + axis.normalize(); + + float angle = acos(a.dot(b)); + + if(parent_rotation != 0) parent_rotation->mult_vec(axis); + + if(invert_increment) + incr.set_value(axis, -angle * tmpscale); + else + incr.set_value(axis, angle * tmpscale); + + } + + // fixme: shouldn't operator*() preserve 'r' in this case? + if(incr[3] != 0) + r = incr * r; + } + + void increment_rotation() + { + // fixme: shouldn't operator*() preserve 'r' in this case? + if(incr[3] != 0) + r = incr * r; + } + + matrix4f get_transform() + { + matrix4f mt, mr, minvt; + mt.set_translate(centroid); + r.get_value(mr); + minvt.set_translate(-centroid); + return mt * mr * minvt; + } + + matrix4f get_inverse_transform() + { + matrix4f mt, mr, minvt; + mt.set_translate(centroid); + r.inverse().get_value(mr); + minvt.set_translate(-centroid); + return mt * mr * minvt; + } + + bool invert_increment; + const rotationf * parent_rotation; + rotationf r; + vec3f centroid; + float scale; + bool legacy_mode; + rotationf incr; + float radius; + vec3f offset; + }; + +} + +#endif diff --git a/ThirdParty/GL/glh/glh_linear.h b/ThirdParty/GL/glh/glh_linear.h new file mode 100644 index 0000000..422c05f --- /dev/null +++ b/ThirdParty/GL/glh/glh_linear.h @@ -0,0 +1,1617 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +/* +glh_linear.h +*/ + +// Author: Cass W. Everitt + +#ifndef GLH_LINEAR_H +#define GLH_LINEAR_H + +#include +#include +#include + +// only supports float for now... +#define GLH_REAL_IS_FLOAT + +#ifdef GLH_REAL_IS_FLOAT +# define GLH_REAL float +# define GLH_REAL_NAMESPACE ns_float +#endif + +#ifdef _WIN32 +# define TEMPLATE_FUNCTION +#else +# define TEMPLATE_FUNCTION <> +#endif + +#define GLH_QUATERNION_NORMALIZATION_THRESHOLD 64 + +#define GLH_RAD_TO_DEG GLH_REAL(57.2957795130823208767981548141052) +#define GLH_DEG_TO_RAD GLH_REAL(0.0174532925199432957692369076848861) +#define GLH_ZERO GLH_REAL(0.0) +#define GLH_ONE GLH_REAL(1.0) +#define GLH_TWO GLH_REAL(2.0) +#define GLH_EPSILON GLH_REAL(10e-6) +#define GLH_PI GLH_REAL(3.1415926535897932384626433832795) + +#define equivalent(a,b) (((a < b + GLH_EPSILON) && (a > b - GLH_EPSILON)) ? true : false) + +namespace glh +{ + + inline GLH_REAL to_degrees(GLH_REAL radians) { return radians*GLH_RAD_TO_DEG; } + inline GLH_REAL to_radians(GLH_REAL degrees) { return degrees*GLH_DEG_TO_RAD; } + + + template + class vec + { + public: + int size() const { return N; } + + vec(const T & t = T()) + { for(int i = 0; i < N; i++) v[i] = t; } + vec(const T * tp) + { for(int i = 0; i < N; i++) v[i] = tp[i]; } + + const T * get_value() const + { return v; } + + + T dot( const vec & rhs ) const + { + T r = 0; + for(int i = 0; i < N; i++) r += v[i]*rhs.v[i]; + return r; + } + + T length() const + { + T r = 0; + for(int i = 0; i < N; i++) r += v[i]*v[i]; + return T(sqrt(r)); + } + + T square_norm() const + { + T r = 0; + for(int i = 0; i < N; i++) r += v[i]*v[i]; + return r; + } + + void negate() + { for(int i = 0; i < N; i++) v[i] = -v[i]; } + + + T normalize() + { + T sum(0); + for(int i = 0; i < N; i++) + sum += v[i]*v[i]; + sum = T(sqrt(sum)); + if (sum > GLH_EPSILON) + for(int i = 0; i < N; i++) + v[i] /= sum; + return sum; + } + + + vec & set_value( const T * rhs ) + { for(int i = 0; i < N; i++) v[i] = rhs[i]; return *this; } + + T & operator [] ( int i ) + { return v[i]; } + + const T & operator [] ( int i ) const + { return v[i]; } + + vec & operator *= ( T d ) + { for(int i = 0; i < N; i++) v[i] *= d; return *this;} + + vec & operator *= ( const vec & u ) + { for(int i = 0; i < N; i++) v[i] *= u[i]; return *this;} + + vec & operator /= ( T d ) + { if(d == 0) return *this; for(int i = 0; i < N; i++) v[i] /= d; return *this;} + + vec & operator += ( const vec & u ) + { for(int i = 0; i < N; i++) v[i] += u.v[i]; return *this;} + + vec & operator -= ( const vec & u ) + { for(int i = 0; i < N; i++) v[i] -= u.v[i]; return *this;} + + + vec operator - () const + { vec rv = v; rv.negate(); return rv; } + + vec operator + ( const vec &v) const + { vec rt(*this); return rt += v; } + + vec operator - ( const vec &v) const + { vec rt(*this); return rt -= v; } + + vec operator * ( T d) const + { vec rt(*this); return rt *= d; } + + //friend bool operator == TEMPLATE_FUNCTION ( const vec &v1, const vec &v2 ); + //friend bool operator != TEMPLATE_FUNCTION ( const vec &v1, const vec &v2 ); + + + //protected: + T v[N]; + }; + + + + // vector friend operators + + template inline + vec operator * ( const vec & b, T d ) + { + vec rt(b); + return rt *= d; + } + + template inline + vec operator * ( T d, const vec & b ) + { return b*d; } + + template inline + vec operator * ( const vec & b, const vec & d ) + { + vec rt(b); + return rt *= d; + } + + template inline + vec operator / ( const vec & b, T d ) + { vec rt(b); return rt /= d; } + + template inline + vec operator + ( const vec & v1, const vec & v2 ) + { vec rt(v1); return rt += v2; } + + template inline + vec operator - ( const vec & v1, const vec & v2 ) + { vec rt(v1); return rt -= v2; } + + + template inline + bool operator == ( const vec & v1, const vec & v2 ) + { + for(int i = 0; i < N; i++) + if(v1.v[i] != v2.v[i]) + return false; + return true; + } + + template inline + bool operator != ( const vec & v1, const vec & v2 ) + { return !(v1 == v2); } + + + typedef vec<3,unsigned char> vec3ub; + typedef vec<4,unsigned char> vec4ub; + + + + + + namespace GLH_REAL_NAMESPACE + { + typedef GLH_REAL real; + + class line; + class plane; + class matrix4; + class quaternion; + typedef quaternion rotation; + + class vec2 : public vec<2,real> + { + public: + vec2(const real & t = real()) : vec<2,real>(t) + {} + vec2(const vec<2,real> & t) : vec<2,real>(t) + {} + vec2(const real * tp) : vec<2,real>(tp) + {} + + vec2(real x, real y ) + { v[0] = x; v[1] = y; } + + void get_value(real & x, real & y) const + { x = v[0]; y = v[1]; } + + vec2 & set_value( const real & x, const real & y) + { v[0] = x; v[1] = y; return *this; } + + }; + + + class vec3 : public vec<3,real> + { + public: + vec3(const real & t = real()) : vec<3,real>(t) + {} + vec3(const vec<3,real> & t) : vec<3,real>(t) + {} + vec3(const real * tp) : vec<3,real>(tp) + {} + + vec3(real x, real y, real z) + { v[0] = x; v[1] = y; v[2] = z; } + + void get_value(real & x, real & y, real & z) const + { x = v[0]; y = v[1]; z = v[2]; } + + vec3 cross( const vec3 &rhs ) const + { + vec3 rt; + rt.v[0] = v[1]*rhs.v[2]-v[2]*rhs.v[1]; + rt.v[1] = v[2]*rhs.v[0]-v[0]*rhs.v[2]; + rt.v[2] = v[0]*rhs.v[1]-v[1]*rhs.v[0]; + return rt; + } + + vec3 & set_value( const real & x, const real & y, const real & z) + { v[0] = x; v[1] = y; v[2] = z; return *this; } + + }; + + + class vec4 : public vec<4,real> + { + public: + vec4(const real & t = real()) : vec<4,real>(t) + {} + vec4(const vec<4,real> & t) : vec<4,real>(t) + {} + + vec4(const vec<3,real> & t, real fourth) + + { v[0] = t.v[0]; v[1] = t.v[1]; v[2] = t.v[2]; v[3] = fourth; } + vec4(const real * tp) : vec<4,real>(tp) + {} + vec4(real x, real y, real z, real w) + { v[0] = x; v[1] = y; v[2] = z; v[3] = w; } + + void get_value(real & x, real & y, real & z, real & w) const + { x = v[0]; y = v[1]; z = v[2]; w = v[3]; } + + vec4 & set_value( const real & x, const real & y, const real & z, const real & w) + { v[0] = x; v[1] = y; v[2] = z; v[3] = w; return *this; } + }; + + inline + vec3 homogenize(const vec4 & v) + { + vec3 rt; + assert(v.v[3] != GLH_ZERO); + rt.v[0] = v.v[0]/v.v[3]; + rt.v[1] = v.v[1]/v.v[3]; + rt.v[2] = v.v[2]/v.v[3]; + return rt; + } + + + + class line + { + public: + + line() + { set_value(vec3(0,0,0),vec3(0,0,1)); } + + line( const vec3 & p0, const vec3 &p1) + { set_value(p0,p1); } + + void set_value( const vec3 &p0, const vec3 &p1) + { + position = p0; + direction = p1-p0; + direction.normalize(); + } + + bool get_closest_points(const line &line2, + vec3 &pointOnThis, + vec3 &pointOnThat) + { + + // quick check to see if parallel -- if so, quit. + if(fabs(direction.dot(line2.direction)) == 1.0) + return 0; + line l2 = line2; + + // Algorithm: Brian Jean + // + register real u; + register real v; + vec3 Vr = direction; + vec3 Vs = l2.direction; + register real Vr_Dot_Vs = Vr.dot(Vs); + register real detA = real(1.0 - (Vr_Dot_Vs * Vr_Dot_Vs)); + vec3 C = l2.position - position; + register real C_Dot_Vr = C.dot(Vr); + register real C_Dot_Vs = C.dot(Vs); + + u = (C_Dot_Vr - Vr_Dot_Vs * C_Dot_Vs)/detA; + v = (C_Dot_Vr * Vr_Dot_Vs - C_Dot_Vs)/detA; + + pointOnThis = position; + pointOnThis += direction * u; + pointOnThat = l2.position; + pointOnThat += l2.direction * v; + + return 1; + } + + vec3 get_closest_point(const vec3 &point) + { + vec3 np = point - position; + vec3 rp = direction*direction.dot(np)+position; + return rp; + } + + const vec3 & get_position() const {return position;} + + const vec3 & get_direction() const {return direction;} + + //protected: + vec3 position; + vec3 direction; + }; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // matrix + + + class matrix4 + { + + public: + + matrix4() { make_identity(); } + + matrix4( real r ) + { set_value(r); } + + matrix4( real * m ) + { set_value(m); } + + matrix4( real a00, real a01, real a02, real a03, + real a10, real a11, real a12, real a13, + real a20, real a21, real a22, real a23, + real a30, real a31, real a32, real a33 ) + { + element(0,0) = a00; + element(0,1) = a01; + element(0,2) = a02; + element(0,3) = a03; + + element(1,0) = a10; + element(1,1) = a11; + element(1,2) = a12; + element(1,3) = a13; + + element(2,0) = a20; + element(2,1) = a21; + element(2,2) = a22; + element(2,3) = a23; + + element(3,0) = a30; + element(3,1) = a31; + element(3,2) = a32; + element(3,3) = a33; + } + + + void get_value( real * mp ) const + { + int c = 0; + for(int j=0; j < 4; j++) + for(int i=0; i < 4; i++) + mp[c++] = element(i,j); + } + + + const real * get_value() const + { return m; } + + void set_value( real * mp) + { + int c = 0; + for(int j=0; j < 4; j++) + for(int i=0; i < 4; i++) + element(i,j) = mp[c++]; + } + + void set_value( real r ) + { + for(int i=0; i < 4; i++) + for(int j=0; j < 4; j++) + element(i,j) = r; + } + + void make_identity() + { + element(0,0) = 1.0; + element(0,1) = 0.0; + element(0,2) = 0.0; + element(0,3) = 0.0; + + element(1,0) = 0.0; + element(1,1) = 1.0; + element(1,2) = 0.0; + element(1,3) = 0.0; + + element(2,0) = 0.0; + element(2,1) = 0.0; + element(2,2) = 1.0; + element(2,3) = 0.0; + + element(3,0) = 0.0; + element(3,1) = 0.0; + element(3,2) = 0.0; + element(3,3) = 1.0; + } + + + static matrix4 identity() + { + static matrix4 mident ( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 ); + return mident; + } + + + void set_scale( real s ) + { + element(0,0) = s; + element(1,1) = s; + element(2,2) = s; + } + + void set_scale( const vec3 & s ) + { + element(0,0) = s.v[0]; + element(1,1) = s.v[1]; + element(2,2) = s.v[2]; + } + + + void set_translate( const vec3 & t ) + { + element(0,3) = t.v[0]; + element(1,3) = t.v[1]; + element(2,3) = t.v[2]; + } + + void set_row(int r, const vec4 & t) + { + element(r,0) = t.v[0]; + element(r,1) = t.v[1]; + element(r,2) = t.v[2]; + element(r,3) = t.v[3]; + } + + void set_column(int c, const vec4 & t) + { + element(0,c) = t.v[0]; + element(1,c) = t.v[1]; + element(2,c) = t.v[2]; + element(3,c) = t.v[3]; + } + + + void get_row(int r, vec4 & t) const + { + t.v[0] = element(r,0); + t.v[1] = element(r,1); + t.v[2] = element(r,2); + t.v[3] = element(r,3); + } + + vec4 get_row(int r) const + { + vec4 v; get_row(r, v); + return v; + } + + void get_column(int c, vec4 & t) const + { + t.v[0] = element(0,c); + t.v[1] = element(1,c); + t.v[2] = element(2,c); + t.v[3] = element(3,c); + } + + vec4 get_column(int c) const + { + vec4 v; get_column(c, v); + return v; + } + + matrix4 inverse() const + { + matrix4 minv; + + real r1[8], r2[8], r3[8], r4[8]; + real *s[4], *tmprow; + + s[0] = &r1[0]; + s[1] = &r2[0]; + s[2] = &r3[0]; + s[3] = &r4[0]; + + register int i,j,p,jj; + for(i=0;i<4;i++) + { + for(j=0;j<4;j++) + { + s[i][j] = element(i,j); + if(i==j) s[i][j+4] = 1.0; + else s[i][j+4] = 0.0; + } + } + real scp[4]; + for(i=0;i<4;i++) + { + scp[i] = real(fabs(s[i][0])); + for(j=1;j<4;j++) + if(real(fabs(s[i][j])) > scp[i]) scp[i] = real(fabs(s[i][j])); + if(scp[i] == 0.0) return minv; // singular matrix! + } + + int pivot_to; + real scp_max; + for(i=0;i<4;i++) + { + // select pivot row + pivot_to = i; + scp_max = real(fabs(s[i][i]/scp[i])); + // find out which row should be on top + for(p=i+1;p<4;p++) + if(real(fabs(s[p][i]/scp[p])) > scp_max) + { scp_max = real(fabs(s[p][i]/scp[p])); pivot_to = p; } + // Pivot if necessary + if(pivot_to != i) + { + tmprow = s[i]; + s[i] = s[pivot_to]; + s[pivot_to] = tmprow; + real tmpscp; + tmpscp = scp[i]; + scp[i] = scp[pivot_to]; + scp[pivot_to] = tmpscp; + } + + real mji; + // perform gaussian elimination + for(j=i+1;j<4;j++) + { + mji = s[j][i]/s[i][i]; + s[j][i] = 0.0; + for(jj=i+1;jj<8;jj++) + s[j][jj] -= mji*s[i][jj]; + } + } + if(s[3][3] == 0.0) return minv; // singular matrix! + + // + // Now we have an upper triangular matrix. + // + // x x x x | y y y y + // 0 x x x | y y y y + // 0 0 x x | y y y y + // 0 0 0 x | y y y y + // + // we'll back substitute to get the inverse + // + // 1 0 0 0 | z z z z + // 0 1 0 0 | z z z z + // 0 0 1 0 | z z z z + // 0 0 0 1 | z z z z + // + + real mij; + for(i=3;i>0;i--) + { + for(j=i-1;j > -1; j--) + { + mij = s[j][i]/s[i][i]; + for(jj=j+1;jj<8;jj++) + s[j][jj] -= mij*s[i][jj]; + } + } + + for(i=0;i<4;i++) + for(j=0;j<4;j++) + minv(i,j) = s[i][j+4] / s[i][i]; + + return minv; + } + + + matrix4 transpose() const + { + matrix4 mtrans; + + for(int i=0;i<4;i++) + for(int j=0;j<4;j++) + mtrans(i,j) = element(j,i); + return mtrans; + } + + matrix4 & mult_right( const matrix4 & b ) + { + matrix4 mt(*this); + set_value(real(0)); + + for(int i=0; i < 4; i++) + for(int j=0; j < 4; j++) + for(int c=0; c < 4; c++) + element(i,j) += mt(i,c) * b(c,j); + return *this; + } + + matrix4 & mult_left( const matrix4 & b ) + { + matrix4 mt(*this); + set_value(real(0)); + + for(int i=0; i < 4; i++) + for(int j=0; j < 4; j++) + for(int c=0; c < 4; c++) + element(i,j) += b(i,c) * mt(c,j); + return *this; + } + + // dst = M * src + void mult_matrix_vec( const vec3 &src, vec3 &dst ) const + { + real w = ( + src.v[0] * element(3,0) + + src.v[1] * element(3,1) + + src.v[2] * element(3,2) + + element(3,3) ); + + assert(w != GLH_ZERO); + + dst.v[0] = ( + src.v[0] * element(0,0) + + src.v[1] * element(0,1) + + src.v[2] * element(0,2) + + element(0,3) ) / w; + dst.v[1] = ( + src.v[0] * element(1,0) + + src.v[1] * element(1,1) + + src.v[2] * element(1,2) + + element(1,3) ) / w; + dst.v[2] = ( + src.v[0] * element(2,0) + + src.v[1] * element(2,1) + + src.v[2] * element(2,2) + + element(2,3) ) / w; + } + + void mult_matrix_vec( vec3 & src_and_dst) const + { mult_matrix_vec(vec3(src_and_dst), src_and_dst); } + + + // dst = src * M + void mult_vec_matrix( const vec3 &src, vec3 &dst ) const + { + real w = ( + src.v[0] * element(0,3) + + src.v[1] * element(1,3) + + src.v[2] * element(2,3) + + element(3,3) ); + + assert(w != GLH_ZERO); + + dst.v[0] = ( + src.v[0] * element(0,0) + + src.v[1] * element(1,0) + + src.v[2] * element(2,0) + + element(3,0) ) / w; + dst.v[1] = ( + src.v[0] * element(0,1) + + src.v[1] * element(1,1) + + src.v[2] * element(2,1) + + element(3,1) ) / w; + dst.v[2] = ( + src.v[0] * element(0,2) + + src.v[1] * element(1,2) + + src.v[2] * element(2,2) + + element(3,2) ) / w; + } + + + void mult_vec_matrix( vec3 & src_and_dst) const + { mult_vec_matrix(vec3(src_and_dst), src_and_dst); } + + // dst = M * src + void mult_matrix_vec( const vec4 &src, vec4 &dst ) const + { + dst.v[0] = ( + src.v[0] * element(0,0) + + src.v[1] * element(0,1) + + src.v[2] * element(0,2) + + src.v[3] * element(0,3)); + dst.v[1] = ( + src.v[0] * element(1,0) + + src.v[1] * element(1,1) + + src.v[2] * element(1,2) + + src.v[3] * element(1,3)); + dst.v[2] = ( + src.v[0] * element(2,0) + + src.v[1] * element(2,1) + + src.v[2] * element(2,2) + + src.v[3] * element(2,3)); + dst.v[3] = ( + src.v[0] * element(3,0) + + src.v[1] * element(3,1) + + src.v[2] * element(3,2) + + src.v[3] * element(3,3)); + } + + void mult_matrix_vec( vec4 & src_and_dst) const + { mult_matrix_vec(vec4(src_and_dst), src_and_dst); } + + + // dst = src * M + void mult_vec_matrix( const vec4 &src, vec4 &dst ) const + { + dst.v[0] = ( + src.v[0] * element(0,0) + + src.v[1] * element(1,0) + + src.v[2] * element(2,0) + + src.v[3] * element(3,0)); + dst.v[1] = ( + src.v[0] * element(0,1) + + src.v[1] * element(1,1) + + src.v[2] * element(2,1) + + src.v[3] * element(3,1)); + dst.v[2] = ( + src.v[0] * element(0,2) + + src.v[1] * element(1,2) + + src.v[2] * element(2,2) + + src.v[3] * element(3,2)); + dst.v[3] = ( + src.v[0] * element(0,3) + + src.v[1] * element(1,3) + + src.v[2] * element(2,3) + + src.v[3] * element(3,3)); + } + + + void mult_vec_matrix( vec4 & src_and_dst) const + { mult_vec_matrix(vec4(src_and_dst), src_and_dst); } + + + // dst = M * src + void mult_matrix_dir( const vec3 &src, vec3 &dst ) const + { + dst.v[0] = ( + src.v[0] * element(0,0) + + src.v[1] * element(0,1) + + src.v[2] * element(0,2) ) ; + dst.v[1] = ( + src.v[0] * element(1,0) + + src.v[1] * element(1,1) + + src.v[2] * element(1,2) ) ; + dst.v[2] = ( + src.v[0] * element(2,0) + + src.v[1] * element(2,1) + + src.v[2] * element(2,2) ) ; + } + + + void mult_matrix_dir( vec3 & src_and_dst) const + { mult_matrix_dir(vec3(src_and_dst), src_and_dst); } + + + // dst = src * M + void mult_dir_matrix( const vec3 &src, vec3 &dst ) const + { + dst.v[0] = ( + src.v[0] * element(0,0) + + src.v[1] * element(1,0) + + src.v[2] * element(2,0) ) ; + dst.v[1] = ( + src.v[0] * element(0,1) + + src.v[1] * element(1,1) + + src.v[2] * element(2,1) ) ; + dst.v[2] = ( + src.v[0] * element(0,2) + + src.v[1] * element(1,2) + + src.v[2] * element(2,2) ) ; + } + + + void mult_dir_matrix( vec3 & src_and_dst) const + { mult_dir_matrix(vec3(src_and_dst), src_and_dst); } + + + real & operator () (int row, int col) + { return element(row,col); } + + const real & operator () (int row, int col) const + { return element(row,col); } + + real & element (int row, int col) + { return m[row | (col<<2)]; } + + const real & element (int row, int col) const + { return m[row | (col<<2)]; } + + matrix4 & operator *= ( const matrix4 & mat ) + { + mult_right( mat ); + return *this; + } + + matrix4 & operator *= ( const real & r ) + { + for (int i = 0; i < 4; ++i) + { + element(0,i) *= r; + element(1,i) *= r; + element(2,i) *= r; + element(3,i) *= r; + } + return *this; + } + + matrix4 & operator += ( const matrix4 & mat ) + { + for (int i = 0; i < 4; ++i) + { + element(0,i) += mat.element(0,i); + element(1,i) += mat.element(1,i); + element(2,i) += mat.element(2,i); + element(3,i) += mat.element(3,i); + } + return *this; + } + + friend matrix4 operator * ( const matrix4 & m1, const matrix4 & m2 ); + friend bool operator == ( const matrix4 & m1, const matrix4 & m2 ); + friend bool operator != ( const matrix4 & m1, const matrix4 & m2 ); + + //protected: + real m[16]; + }; + + inline + matrix4 operator * ( const matrix4 & m1, const matrix4 & m2 ) + { + matrix4 product; + + product = m1; + product.mult_right(m2); + + return product; + } + + inline + bool operator ==( const matrix4 &m1, const matrix4 &m2 ) + { + return ( + m1(0,0) == m2(0,0) && + m1(0,1) == m2(0,1) && + m1(0,2) == m2(0,2) && + m1(0,3) == m2(0,3) && + m1(1,0) == m2(1,0) && + m1(1,1) == m2(1,1) && + m1(1,2) == m2(1,2) && + m1(1,3) == m2(1,3) && + m1(2,0) == m2(2,0) && + m1(2,1) == m2(2,1) && + m1(2,2) == m2(2,2) && + m1(2,3) == m2(2,3) && + m1(3,0) == m2(3,0) && + m1(3,1) == m2(3,1) && + m1(3,2) == m2(3,2) && + m1(3,3) == m2(3,3) ); + } + + inline + bool operator != ( const matrix4 & m1, const matrix4 & m2 ) + { return !( m1 == m2 ); } + + + + + + + + + + + + + + class quaternion + { + public: + + quaternion() + { + *this = identity(); + } + + quaternion( const real v[4] ) + { + set_value( v ); + } + + + quaternion( real q0, real q1, real q2, real q3 ) + { + set_value( q0, q1, q2, q3 ); + } + + + quaternion( const matrix4 & m ) + { + set_value( m ); + } + + + quaternion( const vec3 &axis, real radians ) + { + set_value( axis, radians ); + } + + + quaternion( const vec3 &rotateFrom, const vec3 &rotateTo ) + { + set_value( rotateFrom, rotateTo ); + } + + quaternion( const vec3 & from_look, const vec3 & from_up, + const vec3 & to_look, const vec3& to_up) + { + set_value(from_look, from_up, to_look, to_up); + } + + const real * get_value() const + { + return &q[0]; + } + + void get_value( real &q0, real &q1, real &q2, real &q3 ) const + { + q0 = q[0]; + q1 = q[1]; + q2 = q[2]; + q3 = q[3]; + } + + quaternion & set_value( real q0, real q1, real q2, real q3 ) + { + q[0] = q0; + q[1] = q1; + q[2] = q2; + q[3] = q3; + counter = 0; + return *this; + } + + void get_value( vec3 &axis, real &radians ) const + { + radians = real(acos( q[3] ) * GLH_TWO); + if ( radians == GLH_ZERO ) + axis = vec3( 0.0, 0.0, 1.0 ); + else + { + axis.v[0] = q[0]; + axis.v[1] = q[1]; + axis.v[2] = q[2]; + axis.normalize(); + } + } + + void get_value( matrix4 & m ) const + { + real s, xs, ys, zs, wx, wy, wz, xx, xy, xz, yy, yz, zz; + + real norm = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]; + + s = (equivalent(norm,GLH_ZERO)) ? GLH_ZERO : ( GLH_TWO / norm ); + + xs = q[0] * s; + ys = q[1] * s; + zs = q[2] * s; + + wx = q[3] * xs; + wy = q[3] * ys; + wz = q[3] * zs; + + xx = q[0] * xs; + xy = q[0] * ys; + xz = q[0] * zs; + + yy = q[1] * ys; + yz = q[1] * zs; + zz = q[2] * zs; + + m(0,0) = real( GLH_ONE - ( yy + zz )); + m(1,0) = real ( xy + wz ); + m(2,0) = real ( xz - wy ); + + m(0,1) = real ( xy - wz ); + m(1,1) = real ( GLH_ONE - ( xx + zz )); + m(2,1) = real ( yz + wx ); + + m(0,2) = real ( xz + wy ); + m(1,2) = real ( yz - wx ); + m(2,2) = real ( GLH_ONE - ( xx + yy )); + + m(3,0) = m(3,1) = m(3,2) = m(0,3) = m(1,3) = m(2,3) = GLH_ZERO; + m(3,3) = GLH_ONE; + } + + quaternion & set_value( const real * qp ) + { + memcpy(q,qp,sizeof(real) * 4); + + counter = 0; + return *this; + } + + quaternion & set_value( const matrix4 & m ) + { + real tr, s; + int i, j, k; + const int nxt[3] = { 1, 2, 0 }; + + tr = m(0,0) + m(1,1) + m(2,2); + + if ( tr > GLH_ZERO ) + { + s = real(sqrt( tr + m(3,3) )); + q[3] = real ( s * 0.5 ); + s = real(0.5) / s; + + q[0] = real ( ( m(1,2) - m(2,1) ) * s ); + q[1] = real ( ( m(2,0) - m(0,2) ) * s ); + q[2] = real ( ( m(0,1) - m(1,0) ) * s ); + } + else + { + i = 0; + if ( m(1,1) > m(0,0) ) + i = 1; + + if ( m(2,2) > m(i,i) ) + i = 2; + + j = nxt[i]; + k = nxt[j]; + + s = real(sqrt( ( m(i,j) - ( m(j,j) + m(k,k) )) + GLH_ONE )); + + q[i] = real ( s * 0.5 ); + s = real(0.5 / s); + + q[3] = real ( ( m(j,k) - m(k,j) ) * s ); + q[j] = real ( ( m(i,j) + m(j,i) ) * s ); + q[k] = real ( ( m(i,k) + m(k,i) ) * s ); + } + + counter = 0; + return *this; + } + + quaternion & set_value( const vec3 &axis, real theta ) + { + real sqnorm = axis.square_norm(); + + if (sqnorm <= GLH_EPSILON) + { + // axis too small. + x = y = z = 0.0; + w = 1.0; + } + else + { + theta *= real(0.5); + real sin_theta = real(sin(theta)); + + if (!equivalent(sqnorm,GLH_ONE)) + sin_theta /= real(sqrt(sqnorm)); + x = sin_theta * axis.v[0]; + y = sin_theta * axis.v[1]; + z = sin_theta * axis.v[2]; + w = real(cos(theta)); + } + return *this; + } + + quaternion & set_value( const vec3 & rotateFrom, const vec3 & rotateTo ) + { + vec3 p1, p2; + real alpha; + + p1 = rotateFrom; + p1.normalize(); + p2 = rotateTo; + p2.normalize(); + + alpha = p1.dot(p2); + + if(equivalent(alpha,GLH_ONE)) + { + *this = identity(); + return *this; + } + + // ensures that the anti-parallel case leads to a positive dot + if(equivalent(alpha,-GLH_ONE)) + { + vec3 v; + + if(p1.v[0] != p1.v[1] || p1.v[0] != p1.v[2]) + v = vec3(p1.v[1], p1.v[2], p1.v[0]); + else + v = vec3(-p1.v[0], p1.v[1], p1.v[2]); + + v -= p1 * p1.dot(v); + v.normalize(); + + set_value(v, GLH_PI); + return *this; + } + + p1 = p1.cross(p2); + p1.normalize(); + set_value(p1,real(acos(alpha))); + + counter = 0; + return *this; + } + + quaternion & set_value( const vec3 & from_look, const vec3 & from_up, + const vec3 & to_look, const vec3 & to_up) + { + quaternion r_look = quaternion(from_look, to_look); + + vec3 rotated_from_up(from_up); + r_look.mult_vec(rotated_from_up); + + quaternion r_twist = quaternion(rotated_from_up, to_up); + + *this = r_twist; + *this *= r_look; + return *this; + } + + quaternion & operator *= ( const quaternion & qr ) + { + quaternion ql(*this); + + w = ql.w * qr.w - ql.x * qr.x - ql.y * qr.y - ql.z * qr.z; + x = ql.w * qr.x + ql.x * qr.w + ql.y * qr.z - ql.z * qr.y; + y = ql.w * qr.y + ql.y * qr.w + ql.z * qr.x - ql.x * qr.z; + z = ql.w * qr.z + ql.z * qr.w + ql.x * qr.y - ql.y * qr.x; + + counter += qr.counter; + counter++; + counter_normalize(); + return *this; + } + + void normalize() + { + real rnorm = GLH_ONE / real(sqrt(w * w + x * x + y * y + z * z)); + if (equivalent(rnorm, GLH_ZERO)) + return; + x *= rnorm; + y *= rnorm; + z *= rnorm; + w *= rnorm; + counter = 0; + } + + friend bool operator == ( const quaternion & q1, const quaternion & q2 ); + + friend bool operator != ( const quaternion & q1, const quaternion & q2 ); + + friend quaternion operator * ( const quaternion & q1, const quaternion & q2 ); + + bool equals( const quaternion & r, real tolerance ) const + { + real t; + + t = ( + (q[0]-r.q[0])*(q[0]-r.q[0]) + + (q[1]-r.q[1])*(q[1]-r.q[1]) + + (q[2]-r.q[2])*(q[2]-r.q[2]) + + (q[3]-r.q[3])*(q[3]-r.q[3]) ); + if(t > GLH_EPSILON) + return false; + return 1; + } + + quaternion & conjugate() + { + q[0] *= -GLH_ONE; + q[1] *= -GLH_ONE; + q[2] *= -GLH_ONE; + return *this; + } + + quaternion & invert() + { + return conjugate(); + } + + quaternion inverse() const + { + quaternion r = *this; + return r.invert(); + } + + // + // Quaternion multiplication with cartesian vector + // v' = q*v*q(star) + // + void mult_vec( const vec3 &src, vec3 &dst ) const + { + real v_coef = w * w - x * x - y * y - z * z; + real u_coef = GLH_TWO * (src.v[0] * x + src.v[1] * y + src.v[2] * z); + real c_coef = GLH_TWO * w; + + dst.v[0] = v_coef * src.v[0] + u_coef * x + c_coef * (y * src.v[2] - z * src.v[1]); + dst.v[1] = v_coef * src.v[1] + u_coef * y + c_coef * (z * src.v[0] - x * src.v[2]); + dst.v[2] = v_coef * src.v[2] + u_coef * z + c_coef * (x * src.v[1] - y * src.v[0]); + } + + void mult_vec( vec3 & src_and_dst) const + { + mult_vec(vec3(src_and_dst), src_and_dst); + } + + void scale_angle( real scaleFactor ) + { + vec3 axis; + real radians; + + get_value(axis, radians); + radians *= scaleFactor; + set_value(axis, radians); + } + + static quaternion slerp( const quaternion & p, const quaternion & q, real alpha ) + { + quaternion r; + + real cos_omega = p.x * q.x + p.y * q.y + p.z * q.z + p.w * q.w; + // if B is on opposite hemisphere from A, use -B instead + + int bflip; + if ( ( bflip = (cos_omega < GLH_ZERO)) ) + cos_omega = -cos_omega; + + // complementary interpolation parameter + real beta = GLH_ONE - alpha; + + if(cos_omega >= GLH_ONE - GLH_EPSILON) + return p; + + real omega = real(acos(cos_omega)); + real one_over_sin_omega = GLH_ONE / real(sin(omega)); + + beta = real(sin(omega*beta) * one_over_sin_omega); + alpha = real(sin(omega*alpha) * one_over_sin_omega); + + if (bflip) + alpha = -alpha; + + r.x = beta * p.q[0]+ alpha * q.q[0]; + r.y = beta * p.q[1]+ alpha * q.q[1]; + r.z = beta * p.q[2]+ alpha * q.q[2]; + r.w = beta * p.q[3]+ alpha * q.q[3]; + return r; + } + + static quaternion identity() + { + static quaternion ident( vec3( 0.0, 0.0, 0.0 ), GLH_ONE ); + return ident; + } + + real & operator []( int i ) + { + assert(i < 4); + return q[i]; + } + + const real & operator []( int i ) const + { + assert(i < 4); + return q[i]; + } + + protected: + + void counter_normalize() + { + if (counter > GLH_QUATERNION_NORMALIZATION_THRESHOLD) + normalize(); + } + + union + { + struct + { + real q[4]; + }; + struct + { + real x; + real y; + real z; + real w; + }; + }; + + // renormalization counter + unsigned char counter; + }; + + inline + bool operator == ( const quaternion & q1, const quaternion & q2 ) + { + return (equivalent(q1.x, q2.x) && + equivalent(q1.y, q2.y) && + equivalent(q1.z, q2.z) && + equivalent(q1.w, q2.w) ); + } + + inline + bool operator != ( const quaternion & q1, const quaternion & q2 ) + { + return ! ( q1 == q2 ); + } + + inline + quaternion operator * ( const quaternion & q1, const quaternion & q2 ) + { + quaternion r(q1); + r *= q2; + return r; + } + + + + + + + + + + + class plane + { + public: + + plane() + { + planedistance = 0.0; + planenormal.set_value( 0.0, 0.0, 1.0 ); + } + + + plane( const vec3 &p0, const vec3 &p1, const vec3 &p2 ) + { + vec3 v0 = p1 - p0; + vec3 v1 = p2 - p0; + planenormal = v0.cross(v1); + planenormal.normalize(); + planedistance = p0.dot(planenormal); + } + + plane( const vec3 &normal, real distance ) + { + planedistance = distance; + planenormal = normal; + planenormal.normalize(); + } + + plane( const vec3 &normal, const vec3 &point ) + { + planenormal = normal; + planenormal.normalize(); + planedistance = point.dot(planenormal); + } + + void offset( real d ) + { + planedistance += d; + } + + bool intersect( const line &l, vec3 &intersection ) const + { + vec3 pos, dir; + vec3 pn = planenormal; + real pd = planedistance; + + pos = l.get_position(); + dir = l.get_direction(); + + if(dir.dot(pn) == 0.0) return 0; + pos -= pn*pd; + // now we're talking about a plane passing through the origin + if(pos.dot(pn) < 0.0) pn.negate(); + if(dir.dot(pn) > 0.0) dir.negate(); + vec3 ppos = pn * pos.dot(pn); + pos = (ppos.length()/dir.dot(-pn))*dir; + intersection = l.get_position(); + intersection += pos; + return 1; + } + void transform( const matrix4 &matrix ) + { + matrix4 invtr = matrix.inverse(); + invtr = invtr.transpose(); + + vec3 pntOnplane = planenormal * planedistance; + vec3 newPntOnplane; + vec3 newnormal; + + invtr.mult_dir_matrix(planenormal, newnormal); + matrix.mult_vec_matrix(pntOnplane, newPntOnplane); + + newnormal.normalize(); + planenormal = newnormal; + planedistance = newPntOnplane.dot(planenormal); + } + + bool is_in_half_space( const vec3 &point ) const + { + + if(( point.dot(planenormal) - planedistance) < 0.0) + return 0; + return 1; + } + + + real distance( const vec3 & point ) const + { + return planenormal.dot(point - planenormal*planedistance); + } + + const vec3 &get_normal() const + { + return planenormal; + } + + + real get_distance_from_origin() const + { + return planedistance; + } + + + friend bool operator == ( const plane & p1, const plane & p2 ); + + + friend bool operator != ( const plane & p1, const plane & p2 ); + + //protected: + vec3 planenormal; + real planedistance; + }; + + inline + bool operator == (const plane & p1, const plane & p2 ) + { + return ( p1.planedistance == p2.planedistance && p1.planenormal == p2.planenormal); + } + + inline + bool operator != ( const plane & p1, const plane & p2 ) + { return ! (p1 == p2); } + + + + } // "ns_##GLH_REAL" + + // make common typedefs... +#ifdef GLH_REAL_IS_FLOAT + typedef GLH_REAL_NAMESPACE::vec2 vec2f; + typedef GLH_REAL_NAMESPACE::vec3 vec3f; + typedef GLH_REAL_NAMESPACE::vec4 vec4f; + typedef GLH_REAL_NAMESPACE::quaternion quaternionf; + typedef GLH_REAL_NAMESPACE::quaternion rotationf; + typedef GLH_REAL_NAMESPACE::line linef; + typedef GLH_REAL_NAMESPACE::plane planef; + typedef GLH_REAL_NAMESPACE::matrix4 matrix4f; +#endif + + + + +} // namespace glh + + + +#endif + diff --git a/ThirdParty/GL/glh/glh_mipmaps.h b/ThirdParty/GL/glh/glh_mipmaps.h new file mode 100644 index 0000000..315d65d --- /dev/null +++ b/ThirdParty/GL/glh/glh_mipmaps.h @@ -0,0 +1,157 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000, 2001 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +/* + gluBuild2DMipmaps cannot build mipmaps for textures whose + "format" it does not recognize. This is primarily because + it infers the number of components and how to average them + from the format. This helper eliminates that problem by + factoring out that functionality. + + Cass Everitt + 1-9-01 + +*/ + +#ifndef GLH_MIPMAPS_H +#define GLH_MIPMAPS_H + +#ifdef _WIN32 +# include +#endif + +#ifdef MACOS +#include +#else +#include +#endif + +namespace glh +{ + + template + class tex_indexer2 + { + public: + tex_indexer2(int width, int height, int tuple_size, T * data) : + w(width), h(height), n(tuple_size), d(data) {} + + T * operator()(int i, int j) + { return d + n * (w * j + i); } + private: + int w, h, n; + T * d; + }; + + template + struct generic_filter + { + typedef T element_type; + + generic_filter(int tuplesize, GLenum gltype) : + gl_type(gltype), tuple_size(tuplesize) {} + + void average( T * out, + const T * a, const T * b, + const T * c, const T * d) + { + for(int i=0; i < tuple_size; i++) + { + double in = double(a[i]) + double(b[i]) + double(c[i]) + double(d[i]); + in /= 4; + out[i] = T(in); + } + } + + const GLenum gl_type; + const int tuple_size; + }; + + // fixme: supports non-square textures! + template + void build_2D_mipmaps( GLenum target, GLenum internal_format, + GLsizei w, GLsizei h, GLenum format, + F filter, const void * vdata) + { + + typedef typename F::element_type DataType; + const DataType * in_data = (const DataType *)vdata; + DataType * data = new DataType [w * h * filter.tuple_size]; + + glTexImage2D(target, 0, internal_format, w, h, 0, format, filter.gl_type, (const DataType *)vdata); + + int level = 1; + if( w >= 2 ) w /= 2; + if( h >= 2 ) h /= 2; + bool done = false; + while(! done) + { + tex_indexer2 bg(w*2, h*2, filter.tuple_size, in_data); + tex_indexer2 sm(w , h , filter.tuple_size, data); + for(int j=0; j < h; j++) + { + int J = j * 2; + for(int i=0; i < w; i++) + { + int I = i*2; + filter.average( sm(i,j), + bg(I , J ), bg(I+1, J ), + bg(I , J+1), bg(I+1, J+1)); + } + } + + glTexImage2D(target, level, internal_format, w, h, 0, format, filter.gl_type, data); + + if(w == 1 && h == 1) done = true; + + if( w >= 2 ) w /= 2; + if( h >= 2 ) h /= 2; + level++; + in_data = data; + } + + delete [] data; + } + +} + +#endif diff --git a/ThirdParty/GL/glh/glh_obs.h b/ThirdParty/GL/glh/glh_obs.h new file mode 100644 index 0000000..55f868c --- /dev/null +++ b/ThirdParty/GL/glh/glh_obs.h @@ -0,0 +1,636 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +// This is a file for simple GL helper classes... + +#ifndef GLH_OBS_H +#define GLH_OBS_H + +#ifdef _WIN32 +# include +#endif + +#ifdef MACOS +#include +#include +#else +#include +#include +#endif + +#include + +#include + +namespace glh +{ + class display_list + { + public: + // set managed to true if you want the class to cleanup objects in the destructor + display_list(bool managed = false) + : valid(false), manageObjects(managed) {} + + virtual ~display_list() + { + if (manageObjects) + del(); + } + + void call_list() + { if(valid) glCallList(dlist); } + + void new_list(GLenum mode) + { if(!valid) gen(); glNewList(dlist, mode); } + + void end_list() + { glEndList(); } + + void del() + { if(valid) glDeleteLists(dlist, 1); valid = false; } + + bool is_valid() const { return valid; } + + private: + + void gen() { dlist = glGenLists(1); valid=true; } + + bool valid; + bool manageObjects; + GLuint dlist; + }; + + class lazy_build_display_list + { + public: + // set managed to true if you want the class to cleanup objects in the destructor + lazy_build_display_list(void (* builder)() = 0, bool managed = false) + : valid(false), manageObjects(managed), needs_rebuild(true), build_func(builder) {} + + virtual ~lazy_build_display_list() + { + if (manageObjects) + del(); + } + + void set_build_func( void (* builder)()) + { build_func = builder; } + + void call_list() + { + if(! valid) gen(); + if(needs_rebuild) rebuild_list(); + glCallList(dlist); + } + + void del() + { if(valid) glDeleteLists(dlist, 1); valid = false; needs_rebuild = true;} + + bool is_valid() const { return valid; } + + // something changed, so rebuild list before next call_list() + void rebuild() { needs_rebuild = true; } + + private: + + void gen() { dlist = glGenLists(1); valid=true; } + void rebuild_list() + { + glNewList(dlist, GL_COMPILE); + if(build_func) (* build_func)(); // call list building routine + glEndList(); + } + + bool valid; + bool manageObjects; + bool needs_rebuild; + GLuint dlist; + void (* build_func)(); + }; + + + class tex_object + { + public: + // set managed to true if you want the class to cleanup objects in the destructor + tex_object(GLenum tgt, bool managed) + : target(tgt), valid(false), manageObjects(managed) {} + + virtual ~tex_object() + { + if (manageObjects) + del(); + } + + void bind() + { if(!valid) gen(); glBindTexture(target, texture); } + + // convenience methods + +#ifdef __MACOS_DISABLED__ + void parameter(GLenum pname, int i) + { glTexParameteri(target, pname, i); } +#endif + + void parameter(GLenum pname, GLint i) + { glTexParameteri(target, pname, i); } + + void parameter(GLenum pname, GLfloat f) + { glTexParameterf(target, pname, f); } + + void parameter(GLenum pname, GLint * ip) + { glTexParameteriv(target, pname, ip); } + + void parameter(GLenum pname, GLfloat * fp) + { glTexParameterfv(target, pname, fp); } + + void enable() { glEnable(target); } + void disable() { glDisable(target); } + + + void del() + { if(valid) glDeleteTextures(1, &texture); valid = false; } + bool is_valid() const { return valid; } + + void gen() { glGenTextures(1, &texture); valid=true; } + + GLenum target; + bool valid; + bool manageObjects; + GLuint texture; + }; + + class tex_object_1D : public tex_object + { public: tex_object_1D(bool managed = false) : tex_object(GL_TEXTURE_1D, managed) {} }; + + class tex_object_2D : public tex_object + { public: tex_object_2D(bool managed = false) : tex_object(GL_TEXTURE_2D, managed) {} }; + + class tex_object_3D : public tex_object + { public: tex_object_3D(bool managed = false) : tex_object(GL_TEXTURE_3D, managed) {} }; + + +# ifdef GL_ARB_texture_cube_map + class tex_object_cube_map : public tex_object + { public: tex_object_cube_map(bool managed = false) : tex_object(GL_TEXTURE_CUBE_MAP_ARB, managed) {} }; +# elif GL_EXT_texture_cube_map + class tex_object_cube_map : public tex_object + { public: tex_object_cube_map(bool managed = false) : tex_object(GL_TEXTURE_CUBE_MAP_EXT, managed) {} }; +# endif + +#if defined(GL_EXT_texture_rectangle) + class tex_object_rectangle : public tex_object + { public: tex_object_rectangle(bool managed = false) : tex_object(GL_TEXTURE_RECTANGLE_EXT, managed) {} }; +#elif defined(GL_NV_texture_rectangle) + class tex_object_rectangle : public tex_object + { public: tex_object_rectangle(bool managed = false) : tex_object(GL_TEXTURE_RECTANGLE_NV, managed) {} }; +#endif + + +# ifdef GL_NV_vertex_program + class vertex_program_base + { + public: + // set managed to true if you want the class to cleanup objects in the destructor + vertex_program_base(GLenum tgt, bool managed) + : valid(false), manageObjects(managed), target(tgt) {} + + virtual ~vertex_program_base() + { + if (manageObjects) + del(); + } + + void bind() + { if(!valid) gen(); glBindProgramNV(target, program); } + + void unbind() + { glBindProgramNV(target, 0); } + + void load(GLuint size, const GLubyte * prog_text) + { + if(!valid) gen(); + glLoadProgramNV(target, program, size, prog_text); + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + //bgn < 0 ? 0 : bgn; + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(GLuint size, const char * prog_text) + { + if(!valid) gen(); + glLoadProgramNV(target, program, size, (const GLubyte *) prog_text); + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + //bgn < 0 ? 0 : bgn; + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(const char * prog_text) + { load((GLuint)strlen(prog_text), prog_text); } + + + void del() + { if(valid) glDeleteProgramsNV(1, &program); valid = false; } + + bool is_valid() const { return valid; } + + private: + + void gen() { glGenProgramsNV(1, &program); valid=true; } + + bool valid; + bool manageObjects; + GLenum target; + GLuint program; + }; + + class vertex_program : public vertex_program_base + { + public: + vertex_program(bool managed = false) + : vertex_program_base(GL_VERTEX_PROGRAM_NV, managed) {} + }; + + class vertex_state_program : public vertex_program_base + { + public: + vertex_state_program(bool managed = false) + : vertex_program_base(GL_VERTEX_STATE_PROGRAM_NV, managed) {} + }; + + class lazy_load_vertex_program : public vertex_program_base + { + public: + lazy_load_vertex_program(void (*vp_loader)() = 0, bool managed = false) + : vertex_program_base(GL_VERTEX_PROGRAM_NV, managed), needs_load(true), loader(vp_loader) {} + + void bind() + { + vertex_program_base::bind(); + if(needs_load && loader) + { + (* loader)(); + needs_load = false; + } + } + + void reload() { needs_load = true; } + + private: + bool needs_load; + void (* loader)(); + }; + + +#endif + +# ifdef GL_ARB_vertex_program + class arb_vertex_program_base + { + public: + // set managed to true if you want the class to cleanup objects in the destructor + arb_vertex_program_base(GLenum tgt, bool managed) + : valid(false), manageObjects(managed), target(tgt) {} + + virtual ~arb_vertex_program_base() + { + if (manageObjects) + del(); + } + + void bind() + { if(!valid) gen(); glBindProgramARB(target, program); } + + void unbind() + { glBindProgramARB(target, 0); } + + void load(GLuint size, const GLubyte * prog_text) + { + if(!valid) gen(); + bind(); + glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, size, prog_text); + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + //bgn < 0 ? 0 : bgn; + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(GLuint size, const char * prog_text) + { + if(!valid) gen(); + bind(); + glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, size, prog_text); + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + //bgn < 0 ? 0 : bgn; + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(const char * prog_text) + { load((GLuint)strlen(prog_text), prog_text); } + + + void del() { if(valid) glDeleteProgramsARB(1, &program); valid = false; } + + bool is_valid() const { return valid; } + + private: + + void gen() { glGenProgramsARB(1, &program); valid=true; } + + bool valid; + bool manageObjects; + GLenum target; + GLuint program; + }; + + class arb_vertex_program : public arb_vertex_program_base + { + public: + arb_vertex_program(bool managed = false) + : arb_vertex_program_base(GL_VERTEX_PROGRAM_ARB, managed) {} + }; + + class lazy_load_arb_vertex_program : public arb_vertex_program_base + { + public: + lazy_load_arb_vertex_program(void (*vp_loader)() = 0, bool managed = false) + : arb_vertex_program_base(GL_VERTEX_PROGRAM_ARB, managed), needs_load(true), loader(vp_loader) {} + + void bind() + { + arb_vertex_program_base::bind(); + if(needs_load && loader) + { + (* loader)(); + needs_load = false; + } + } + + void reload() { needs_load = true; } + + private: + bool needs_load; + void (* loader)(); + }; + +#endif + +# ifdef GL_NV_fragment_program + + class fragment_program + { + public: + // set managed to true if you want the class to cleanup objects in the destructor + fragment_program(bool managed = false) + : valid(false), manageObjects(managed) {} + + virtual ~fragment_program() + { + if (manageObjects) + del(); + } + + void bind() + { if(!valid) gen(); glBindProgramNV(GL_FRAGMENT_PROGRAM_NV, program); } + + void load(GLuint size, const GLubyte * prog_text) + { + if(!valid) gen(); + glLoadProgramNV(GL_FRAGMENT_PROGRAM_NV, program, size, prog_text); + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + //bgn < 0 ? 0 : bgn; + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(GLuint size, const char * prog_text) + { + if(!valid) gen(); + glLoadProgramNV(GL_FRAGMENT_PROGRAM_NV, program, size, (const GLubyte *) prog_text); + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(const char * prog_text) + { load((GLuint)strlen(prog_text), prog_text); } + + void del() + { if(valid) glDeleteProgramsNV(1, &program); valid = false; } + + bool is_valid() const { return valid; } + + GLuint program; + private: + + void gen() { glGenProgramsNV(1, &program); valid=true; } + + bool valid; + bool manageObjects; + }; + + +#endif + +#ifdef GL_ARB_fragment_program + + class arb_fragment_program + { + public: + // set managed to true if you want the class to cleanup objects in the destructor + arb_fragment_program(bool managed = false) + : valid(false), manageObjects(managed) {} + + virtual ~arb_fragment_program() + { + if (manageObjects) + del(); + } + + void bind() + { if(!valid) gen(); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program); } + + void load(GLuint size, const GLubyte * prog_text) + { + if(!valid) gen(); + glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, size, prog_text); + //glLoadProgramNV(GL_FRAGMENT_PROGRAM_NV, program, size, prog_text); + + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + //bgn < 0 ? 0 : bgn; + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(GLuint size, const char * prog_text) + { + if(!valid) gen(); + glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, size, prog_text); + //glLoadProgramNV(GL_FRAGMENT_PROGRAM_NV, program, size, (const GLubyte *) prog_text); + GLint errpos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errpos); + if(errpos != -1) + { + fprintf(stderr, "program error:\n"); + int bgn = errpos - 10; + + const char * c = (const char *)(prog_text + bgn); + for(int i = 0; i < 30; i++) + { + if(bgn+i >= int(size-1)) + break; + fprintf(stderr, "%c", *c++); + } + fprintf(stderr, "\n"); + } + } + + void load(const char * prog_text) + { load((GLuint)strlen(prog_text), prog_text); } + + void del() + { if(valid) glDeleteProgramsARB(1, &program); valid = false; } + + bool is_valid() const { return valid; } + + GLuint program; + private: + + void gen() { glGenProgramsARB(1, &program); valid=true; } + + bool valid; + bool manageObjects; + }; + + +#endif + +} +#endif diff --git a/ThirdParty/GL/glh/glh_text.h b/ThirdParty/GL/glh/glh_text.h new file mode 100644 index 0000000..bd9bc42 --- /dev/null +++ b/ThirdParty/GL/glh/glh_text.h @@ -0,0 +1,197 @@ +/* + glh - is a platform-indepenedent C++ OpenGL helper library + + + Copyright (c) 2000 Cass Everitt + Copyright (c) 2000 NVIDIA Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * The names of contributors to this software may not be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Cass Everitt - cass@r3.nu +*/ + +#ifndef GLH_TEXT_H +#define GLH_TEXT_H + +#include + +#ifdef _WIN32 +# include +#endif + +#ifdef MACOS +#include +#else +#include +#endif + +#include + +namespace glh +{ + + struct font + { + // build display lists and the like + virtual void initialize() = 0; + // get font metrics + virtual float get_ascent() = 0; + virtual float get_descent() = 0; + virtual float get_width(int i) = 0; + + // draw + virtual void render(int i) = 0; + }; + + inline float string_width(font * f, string text) + { + float w = 0; + for(unsigned int i=0; i < text.size(); i++) + { + w += f->get_width(text[i]); + } + return w; + } + // skip a line + inline void next_line(font * f) + { + glTranslatef(0, - (f->get_ascent() + f->get_descent()), 0); + } + + // renders text horizontally + inline void render_single_line(font * f, string text) + { + glPushMatrix(); + for(unsigned int i=0; i < text.size(); i++) + { + f->render(text[i]); + } + glPopMatrix(); + } + + + // render text on multiple lines (based on newlines) + struct simple_multi_line_text + { + public: + simple_multi_line_text() : f(0), line_spacing(1.1f) {} + + + void render() + { + if(!f) return; + if(dirty) + { + dirty = false; + dl.new_list(GL_COMPILE); + make_render_calls(); + dl.end_list(); + } + dl.call_list(); + } + void get_dimensions(float & width, float & height) + { + if(!f) return; + if(dirty) + { + dirty = false; + dl.new_list(GL_COMPILE); + make_render_calls(); + dl.end_list(); + } + width = w; + height = h; + } + void set_font(font * new_font) + { + dirty = true; + f = new_font; + } + void set_text(const string & new_text) + { + dirty = true; + text = new_text; + } + font * get_font() { return f; } + const string & get_text() { return text; } + private: + // skip a line + void next_line() + { + glTranslatef(0, - (f->get_ascent() + f->get_descent()) * line_spacing, 0); + } + + void make_render_calls() + { + w = h = 0; + float curr_width = 0; + float font_height = f->get_ascent() + f->get_descent(); + int lines = 0; + + if(text.size() != 0) lines++; + + glPushMatrix(); // vertical translation + glPushMatrix(); // horizontal translation + for(unsigned int i=0; i < text.size(); i++) + { + if(text[i] == '\n') + { + lines++; + if(curr_width > w) w = curr_width; + curr_width = 0; + + glPopMatrix(); // back to beginning of line + next_line(); // translate futher down + glPushMatrix(); + } + f->render(text[i]); + curr_width += f->get_width(text[i]); + } + glPopMatrix(); + glPopMatrix(); + if(curr_width > w) w = curr_width; + h = lines * font_height * line_spacing; + } + + font * f; + string text; + display_list dl; + float w, h; + bool dirty; + float line_spacing; + }; + + +} + +#endif diff --git a/ThirdParty/GL/glut32.dll b/ThirdParty/GL/glut32.dll new file mode 100644 index 0000000000000000000000000000000000000000..b141eb4c6a749f7a0d8a7761be1684a49e60def8 GIT binary patch literal 169984 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%0Ae2{3E*Z^QI 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